今更な話なんだけど、node.jsでbcryptを使うメモ。
詳しい話はぐぐれば無限に出てくるからそちらを見てもらうとして、大雑把に、以下のような感じ。
レインボーテーブル対策としてのソルトと、計算能力の向上に対応するためのストレッチングを組み込んでおり、結果文字列さえ管理しておけば、この2要素は自前で管理しなくて良いので、都合が良い。
何はともあれ、インストール。
$ npm install -s bcrypt
かんたんに使ってみる。
const bcrypt = require('bcrypt');
const rawPassword = 'hogehoge';
// ハッシュの生成
const bcryptPassword = bcrypt.hashSync(rawPassword, 10);
// ハッシュの比較
const result = bcrypt.compareSync(rawPassword, bcryptPassword);
これで、bcryptPassword
にはハッシュ化された値(例えば$2b$10$iW/oRJPV1LMHSgTWxbVtZu8y2K8FJWjZ03jOJhFfmKA021ZwrvpmW
みたいな感じ)が入るし、result
にはtrue/false
(ここでは同じパスワードなのでtrue
)が入る。
ちなみに、上記のやり方だとハッシュの生成のときにソルトが自動生成されるので、bcrypt.hashSync
の結果同士の比較ではうまくいかないことに注意。
前掲の例ではhashSync
の第2引数に10
を指定している。これがストレッチングの回数を決定するファクターのようなもので、「2^10
回」みたいに指数に効いてくる。こいつを適当にあげていくことで、計算能力の向上に対処するわけだ。
そもそものBlowfishの話をきちんと理解していないのでアレなのだけど、単純に思い浮かべる「複数回ハッシュ関数にかける」というのとは違って、Blowfishの鍵セットアップにおいてストレッチングをするらしい。鍵とか初期化とかちゃんと中身を理解しないと書けなさそうなので、今度勉強しましょう。
ちなみに、今回使っているbcrypt
パッケージでは、第2引数に0
を指定した場合、デフォルト値として10
が利用される。
値の検討は性能要件と処理能力との天秤にかける必要がある。以下みたいにして処理時間を計測してみよう。
$ cat test.js
const bcrypt = require('bcrypt');
const hrstart = process.hrtime();
bcrypt.hashSync('hogehoge', 10);
const hrend = process.hrtime(hrstart);
console.log('%ds %dms', hrend[0], hrend[1]/1000000);
$ node test.js
0s 66.523834ms
今回使っているbcrypt
パッケージは、hashSync
の第2引数に数値が入力されている場合は、ソルトが自動生成される。
使いたいソルトがある場合にはそれを指定すれば良い。
今回使っているbcrypt
パッケージは、入力である文字列(前掲の例ではhogehoge
)の先頭72バイトを切り出して利用するっぽい。(Wikipedia情報だが、他の実装でもそういうパターンが多いらしい)
参考:https://github.com/kelektiv/node.bcrypt.js/blob/master/src/bcrypt.cc(220行目あたり)
入力長を制限したくないなぁという思いがあると、一回ハッシュ関数に通してから入力すれば良いような気がする。
SHA-512なら出力は512bit(64byte)なので72byteに収まるやんけ!と思うけれど、そちらもあんまりよろしくないらしい。
SHA-512をHEXで出力すると128byte使うので半分近くの情報を失うし、仮にbase64でエンコードしても88byte使うのでやはり切り捨てられる。バイナリとして利用しても、例えばPHPのbcrypt実装はバイナリセーフではないため問題が生じるらしい。
参考:bcryptの72文字制限をSHA-512ハッシュで回避する方式の注意点 | 徳丸浩の日記
今回使っているbcrypt
パッケージがバイナリセーフかどうかは調査していないが、難しいこと考えたくない民としては、SHA-256を使えば良いのでは?とかいう気はする。
前掲のコード例では、生成にしろ比較にしろ同期で書いているが、マニュアルでは非同期が推奨されている。
ストレッチングをするあたり、ハッシュの生成や比較には結構時間がかかるため、これを同期で処理させるとメインのイベントループをブロックしてしまう。というわけで、サーバアプリケーションだった場合には、その間のリクエストに応答できないといった問題が生じる。
マニュアルの要約みたいになっちゃったけど、まぁそれが自分用メモってやつよね。
おわり。