なぜ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.");
});
}
});
長いように見えるが過去に見たコードが現れるので後半は省略するが一応貼り付けておく。
まずは定数の部分。
// 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で扱う場合は最もシンプルで扱いやすいのだ(足し算などがやりやすい)
今はこれだけ覚えておけば十分だ。
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()はここで詳しく確認できる。
const sign = crypto.createHmac("sha256", secret).update(`${now}${nonce}`).digest("hex");
これは認証コードを生成している。あまり難しく考えると沼にハマるので「暗号化している」程度で覚えておけば良いが、今回のメインとなるので私なりに説明してみる。
createHmac()
には第一引数に暗号化アルゴリズム、第二引数に暗号化で使うキー(文字列:秘密鍵)を入力する。
第二引数が文字列ではなく変数secret
になっており混乱するが、このプログラムの冒頭でbitFlyerから取得して各自でコピペしてあるstringで
あり「シークレットキー」だ。
さて、この行は実は私も理解するのにしばらく時間がかかった。 HMACの定義についてはこちらが参考になる。
createHmac("sha256", secret)
まずはsha256アルゴリズムとキーを指定する。まだ認証コードは生成されていない。
update(${now}${nonce})
now
とnounce
をつなげ合わせた文字列(バレても良い公開鍵)を使って認証コードを生成する。${}
という文法についてはテンプレート文字列を参照。
この時点でコードは不可逆となる(通信を傍受してもsecret
は解読できない)
digest("hex")
このコードは16進数を文字列に戻すだけ。
サーバー側もsecret
を知っているのでクライアントからnow
とnonce
を受け取って同じアルゴリズムを実行。 それがクライアントから受け取った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を渡しており、これも繋げたものが秘密鍵としていると推測できる。こちらも漏れて良い。
最後に暗号化アルゴリズムによって生成された認証キーを付け加えているが、こちらも**「不可逆」であるため最悪漏れてもシークレットキーがバレることはない**(通信は割り込まれるかもしれないが)
★漏れて良いと言っても何があるかわからない。基本どれも漏れないように扱うこと。
さて、ここまでで分かることはシークレットキーが漏れるとマズいということだ。
これがバレてしまうと暗号化は無意味になり誰でも認証コードが作れてしまう。くれぐれも注意したい。
残りのコードは以前説明したものとほぼ同じためこれ以上は説明しない。
今回は暗号化して認証するための仕組みについて軽く説明したが、このあたりは非常に専門的知識が必要となるため私も正直危うい。
理解しておいて損はないが、このあたりは必要になった時にまた学習すればよいだろう。
暗号通貨の自動取引ではあるが、暗号化を覚える必要はない。
今回は以上だ。