2020-03-17

Node.jsで仮想通貨の自動取引(bitFlyer編 #6) Realtime API公式コードを読む(4)

今回少し難しい暗号化について説明する。通信を傍受されてもキーが漏れない仕組みだ。

Article Image

今回の目標

なぜAPIキーとシークレットキーに分かれているのかを理解できる。 絶対必要ではないがセキュアな通信について少しでも理解を深めたい場合は今回は良い教材になるのではないだろうか。

今回のコード

本日もbitFlyer Realtime APIのサンプルコードから一部抜粋。

    // authentication parameters
    const now = Date.now();
    const nonce = crypto.randomBytes(16).toString("hex");
    const sign = crypto.createHmac("sha256", secret).update(`${now}${nonce}`).digest("hex");

    // request auth
    socket.emit("auth", {
        api_key: key,
        timestamp: now,
        nonce: nonce,
        signature: sign
    }, err => {
        if (err) {
            console.error("auth", "Authentication Error:", err);
            return;
        }
        console.log("auth", "Authenticated.");

        // subscribe to the Private Channels
        for (const ch of privateChannels) {
            socket.emit("subscribe", ch, err => {
                if (err) {
                    console.error(ch, "Subscribe Error:", err);
                    return;
                }
                console.log(ch, "Subscribed.");
            });
        }
    });

長いように見えるが過去に見たコードが現れるので後半は省略するが一応貼り付けておく。

Dateオブジェクト

まずは定数の部分。

// authentication parameters
    const now = Date.now();
    const nonce = crypto.randomBytes(16).toString("hex");
    const sign = crypto.createHmac("sha256", secret).update(`${now}${nonce}`).digest("hex");

突然 Dateが現れた。実はこれはJavaScriptが予め用意している日付処理用のオブジェクトである。

JavaScriptでは頻繁に使うオブジェクトである。しかし細かい設定まで覚えようとすると膨大な時間を要するのでここでは簡単に説明する。

公式より引用

Date.now()

現在の時刻に対応する数値、すなわち UTC の 1970 年 1 月 1 日 00:00:00 から経過したミリ秒 (閏秒は無視) を表す数値を返します。

ちょっと分かりにくいかもしれないがこれは 1970 年 1 月 1 日 を 0 として「ミリ秒」でカウントした累積が表示される。結構な桁数の数字だ。

例えば次のコードを実行すると

const now = Date.now();
console.log(now);

// 結果
// 1584447219796

このように単なる数字が表示される。これが時間を表している。

人間にはよくわからないがPCで扱う場合は最もシンプルで扱いやすいのだ(足し算などがやりやすい)

今はこれだけ覚えておけば十分だ。

crypto.randomBytes

const nonce = crypto.randomBytes(16).toString("hex");

ここからNode.jsでインストールなしでrequireできるcryptoモジュールの説明になる。

まずはじめにrandomBytes(16)

これはあまり難しく考える必要はなく16進数でランダムな文字列を得るコードだ。JavasScriptのMath.Random()よりセキュアらしい。

次にtoString("hex")という命令が付いている。toStringは初心者でも分かる「文字列に変換」するメソッドだが見慣れない"hex"という引数を指定している。

ちょっと分かりにくいので次のコードを実行して試してみよう。

const nonce = crypto.randomBytes(16)
console.log(nonce)
console.log(nonce.toString("hex"))

// 実行結果
// <Buffer 31 fb 46 fa 60 f7 35 17 ab 41 47 62 4a ec 77 a4>
// 31fb46fa60f73517ab4147624aec77a4

1つ目の実行結果はtoString("hex")を付けなかった場合。 2つ目は付けた場合だ。

これに関しては16進数の「バイナリデータ」を文字列に直接変換しているだけだ。 ただ、難しく考えすぎると沼に陥るので今回はここまでの理解で十分だ。

興味があればNode.jsのBuffer.toString()はここで詳しく確認できる。

crypto.createHmac

const sign = crypto.createHmac("sha256", secret).update(`${now}${nonce}`).digest("hex");

これは認証コードを生成している。あまり難しく考えると沼にハマるので「暗号化している」程度で覚えておけば良いが、今回のメインとなるので私なりに説明してみる。

一応解説(スルーして良い)

createHmac()には第一引数に暗号化アルゴリズム、第二引数に暗号化で使うキー(文字列:秘密鍵)を入力する。

第二引数が文字列ではなく変数secretになっており混乱するが、このプログラムの冒頭でbitFlyerから取得して各自でコピペしてあるstringであり「シークレットキー」だ。

さて、この行は実は私も理解するのにしばらく時間がかかった。 HMACの定義についてはこちらが参考になる。

  1. createHmac("sha256", secret)

    まずはsha256アルゴリズムとキーを指定する。まだ認証コードは生成されていない。

  2. update(${now}${nonce})

    nownounceをつなげ合わせた文字列(バレても良い公開鍵)を使って認証コードを生成する。${}という文法についてはテンプレート文字列を参照。

    この時点でコードは不可逆となる(通信を傍受してもsecretは解読できない)

  3. digest("hex")

    このコードは16進数を文字列に戻すだけ。

  4. サーバー側もsecretを知っているのでクライアントからnownonceを受け取って同じアルゴリズムを実行。 それがクライアントから受け取ったsignと一致すれば認証成功。

ここだけ理解しておく

つまりこのコードによって「通信を傍受されても秘密鍵は解読できない」状態にしているのだ。

公衆のWi-Fiなどで通信してしまってもある程度は大丈夫ということだ(公衆の回線で使用するのは危険極まりないのでやらないこと)

間違っている可能性があるのでその場合は連絡していただければありがたい。

次のコード

サーバー側でも認証コードを生成してクライアントのものと一致するか確認する必要があるため次の定数を送信するが必要となる。それだけだ。

    // request auth
    socket.emit("auth", {
        api_key: key,
        timestamp: now,
        nonce: nonce,
        signature: sign
    }, err => { //省略

api_keyとしてkey(APIキー)は生で入っている。サーバーはこのAPIキーに紐付いているシークレットキーを自らの引き出しから取ってくるだけだ。つまりbitFlyerのAPIキー予め漏れる可能性を考慮しで作られていると考えて良い。ログインIDみたいなものだろう。

次にnow,nonceを渡しており、これも繋げたものが秘密鍵としていると推測できる。こちらも漏れて良い。

最後に暗号化アルゴリズムによって生成された認証キーを付け加えているが、こちらも**「不可逆」であるため最悪漏れてもシークレットキーがバレることはない**(通信は割り込まれるかもしれないが)

★漏れて良いと言っても何があるかわからない。基本どれも漏れないように扱うこと。

重要な事

さて、ここまでで分かることはシークレットキーが漏れるとマズいということだ。

これがバレてしまうと暗号化は無意味になり誰でも認証コードが作れてしまう。くれぐれも注意したい。

理解頂けただろうか?

残りのコードは以前説明したものとほぼ同じためこれ以上は説明しない。

今回は暗号化して認証するための仕組みについて軽く説明したが、このあたりは非常に専門的知識が必要となるため私も正直危うい。

理解しておいて損はないが、このあたりは必要になった時にまた学習すればよいだろう。

暗号通貨の自動取引ではあるが、暗号化を覚える必要はない。

今回は以上だ。



この記事をシェア


謎の技術研究部 (謎技研)