お茶漬けぶろぐ

node.jsでbcryptを使う

今更な話なんだけど、node.jsでbcryptを使うメモ。

bcryptってなに

詳しい話はぐぐれば無限に出てくるからそちらを見てもらうとして、大雑把に、以下のような感じ。

レインボーテーブル対策としてのソルトと、計算能力の向上に対応するためのストレッチングを組み込んでおり、結果文字列さえ管理しておけば、この2要素は自前で管理しなくて良いので、都合が良い。

取り敢えず使う

何はともあれ、インストール。

bcrypt - npm

$ 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引数に数値が入力されている場合は、ソルトが自動生成される。
使いたいソルトがある場合にはそれを指定すれば良い。

72byte

今回使っている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を使えば良いのでは?とかいう気はする。

同期/非同期

前掲のコード例では、生成にしろ比較にしろ同期で書いているが、マニュアルでは非同期が推奨されている。
ストレッチングをするあたり、ハッシュの生成や比較には結構時間がかかるため、これを同期で処理させるとメインのイベントループをブロックしてしまう。というわけで、サーバアプリケーションだった場合には、その間のリクエストに応答できないといった問題が生じる。

おわり

マニュアルの要約みたいになっちゃったけど、まぁそれが自分用メモってやつよね。
おわり。

< M1 Macでterminalをx86_64からarm64に切り替える

ahamo契約したよ >