Node.jsでbcryptでパスワードをハッシュ化する
日付 タグ node.js カテゴリ node.js目次
bcryptでハッシュ化
ユーザー情報などをDBに保存する場合、平文で保存したりするとよろしくないので、ハッシュ値とか何かしらのより安全な状態にして保存するようなことは多々利用例としてあるだろう。そんな時に簡単なハッシュ化をするためにNode.jsではbcryptが利用できるので、この使い方をテストしてみたい。
https://ja.wikipedia.org/wiki/Bcrypt - Bcrypt
bcrypt(ビー・クリプト)はNiels ProvosとDavid Mazièresによって設計された 1999年にUSENIXにて公開された、Blowfish暗号を基盤としたパスワードハッシュ化関数である[1]。 レインボーテーブル攻撃に対抗するためにソルトを組み込んでいる以外に、bcryptは適応的な特性を備えている。 計算能力が増えたとしてもブルートフォース攻撃に耐えられるように、繰り返し回数を増やして速度を落とせるようになっている。
なお、今回作ったサンプルのソースコードは参照用としてGithubの以下に置いてある。 https://github.com/hugodeblog/node-bcrypt
bcryptのインストールとサンプルコード
今回もまずはサンプルディレクトリの作成とパッケージインストールから
$ mkdir node-bcrypt
$ cd node-bcrypt/
$ npm init --yes
$ npm install --save bcrypt
これでbcryptを使える準備が整った。
package.json を見てみると、本記事執筆時点ではバージョンは5.0.0のようだ。
さて、簡単なサンプルとして、bcryptを利用するcrypt.mjs を作成した。
crypt.mjs
import { default as bcrypt } from 'bcrypt';
const saltRounds = 10;
//ハッシュを返す
async function hashpass(password) {
let salt = await bcrypt.genSalt(saltRounds);
let hashed = await bcrypt.hash(password, salt);
return hashed;
}
//パスワードハッシュの比較
async function verifypass(passA, passB) {
let pwcheck = await bcrypt.compare(passA, passB);
return pwcheck;
}
(async () => {
console.log('bcrypt test1 start');
const passTest = 'abc123';
let hashTest = await hashpass(passTest);
console.log(`${passTest} ===> ${hashTest} :ハッシュ値が生成されました`);
var checkValue;
var checkResult;
checkValue = 'abc123';
checkResult = await verifypass(checkValue, hashTest);
if(checkResult) {
console.log(`${checkValue} <===> ${hashTest} :ハッシュ的に一致します`);
}
else {
console.log(`${checkValue} <===> ${hashTest} :ハッシュ的に一致しません`);
}
checkValue = 'abc1234';
checkResult = await verifypass(checkValue, hashTest);
if(checkResult) {
console.log(`${checkValue} <===> ${hashTest} :ハッシュ的に一致します`);
}
else {
console.log(`${checkValue} <===> ${hashTest} :ハッシュ的に一致しません`);
}
console.log('bcrypt test2 start');
// ハッシュ値は毎回違う
let hashTest2 = await hashpass(passTest);
console.log(`${passTest} ===> ${hashTest2} :ハッシュ値が生成されました`);
checkValue = 'abc123';
checkResult = await verifypass(checkValue, hashTest2);
if(checkResult) {
console.log(`${checkValue} <===> ${hashTest2} :ハッシュ的に一致します`);
}
else {
console.log(`${checkValue} <===> ${hashTest2} :ハッシュ的に一致しません`);
}
checkValue = 'abc1234';
checkResult = await verifypass(checkValue, hashTest2);
if(checkResult) {
console.log(`${checkValue} <===> ${hashTest2} :ハッシュ的に一致します`);
}
else {
console.log(`${checkValue} <===> ${hashTest2} :ハッシュ的に一致しません`);
}
console.log('bcrypt test all end');
})();
bcryptを実際に使っているのは以下。
import { default as bcrypt } from 'bcrypt'; const saltRounds = 10; //ハッシュを返す async function hashpass(password) { let salt = await bcrypt.genSalt(saltRounds); let hashed = await bcrypt.hash(password, salt); return hashed; } //パスワードハッシュの比較 async function verifypass(passA, passB) { let pwcheck = await bcrypt.compare(passA, passB); return pwcheck; }
import文でパッケージを読み込み、saltRoundsを10に設定している。
bcryptは2のsaltRounds数だけ処理を繰り返してハッシュ化するので、これを大きくするとより強固になるが、大きくしするぎると計算時間がその分かかってしまうようになるので注意。
あとパスワードと、ハッシュ化したパスワードが一致するかどうかは直接の文字列判定ではできない。
それというのも、bcryptでは脆弱性対策のため、アルゴリズム上、毎回違うハッシュ値が作られるためだ。そのためパスワードと、ハッシュ化したパスワードが一致するかどうかには、bcryptのパッケージにあるcompare関数を利用することになる。
bcryptを利用したサンプルコードの実行確認
さて、今しがたのサンプルを実行してみよう。
$ node crypt.mjs
bcrypt test1 start
abc123 ===> $2b$10$.brVc4i9WXNhtOH7jukTeuHn3kGOd.ZI9LgUnMwND/fPcBd7s4Bwq :ハッシュ値が生成されました
abc123 <===> $2b$10$.brVc4i9WXNhtOH7jukTeuHn3kGOd.ZI9LgUnMwND/fPcBd7s4Bwq :ハッシュ的に一致します
abc1234 <===> $2b$10$.brVc4i9WXNhtOH7jukTeuHn3kGOd.ZI9LgUnMwND/fPcBd7s4Bwq :ハッシュ的に一致しません
bcrypt test2 start
abc123 ===> $2b$10$CDRBI5a.Eqfekfa1HVDro.1uxFr2JtVDtzvsKrYQDq35VpBlwCRU6 :ハッシュ値が生成されました
abc123 <===> $2b$10$CDRBI5a.Eqfekfa1HVDro.1uxFr2JtVDtzvsKrYQDq35VpBlwCRU6 :ハッシュ的に一致します
abc1234 <===> $2b$10$CDRBI5a.Eqfekfa1HVDro.1uxFr2JtVDtzvsKrYQDq35VpBlwCRU6 :ハッシュ的に一致しません
bcrypt test all end
同じパスワード"abc123"をハッシュ化しても、
1度目の生成ハッシュ値
$2b$10$.brVc4i9WXNhtOH7jukTeuHn3kGOd.ZI9LgUnMwND/fPcBd7s4Bwq
2度目の生成ハッシュ値
$2b$10$CDRBI5a.Eqfekfa1HVDro.1uxFr2JtVDtzvsKrYQDq35VpBlwCRU6
この2つが異なることが確認できる。このために一致性検証にはcompare関数を利用する必要があるのだ。
以上、Node.jsでbryptパッケージを利用する方法を見てきた。基本的な利用方法はそれほど難しいものではないので、今後利用していこうと思っている。