不動の鳥の勉強記録

時間があるときに勉強したことをメモします。

bcryptを使ってパスワードをハッシュ値に変換する

会員認証アプリケーションを作成する際、たいていの場合は会員認証にIDとパスワードを用いて認証を行います。
教科書ではパスワードは平文で保管してはいけないと記載があり、
ハッシュ値に変換するなどの手法を紹介しています。
そこで今回はNode.jsでハッシュ値に変換するbcryptの使い方を勉強しました。

■はじめに:パスワードはハッシュ値にするだけでいいの?

私が大学生の時の授業ではとりあえずMD5関数を利用してハッシュ値にしておけ!みたいな時代でしたが、
最近はレインボー攻撃というものが登場しているとのことです。
この攻撃では入手したハッシュ値をあらかじめメッセージをハッシュ値に変換しておいたテーブルとぶつけ、
ハッシュ値にする前のメッセージを入手する手法とのことです。
この対策として下記2つの対応方法がありました。

・saltを付与する

 ハッシュ値にするメッセージをm、ハッシュ値の関数をh(x)、saltをsとしたときに、
 ハッシュ値=h(m||s)として、saltを追加したメッセージをハッシュ化することで、
 元のメッセージの解析を難しくする手法とのことです。
 saltをどうするべきか(メッセージの前に付与する?後ろに付与する?saltはユーザごとに作成すべき?など)は、
 いろいろな方法がありますが、計算機の進化でハッシュ値計算も時間がかからなくなってしまってきているので、
 しないよりした方がいいよ!という観点で深く考えすぎないほうがいいかと思います。
 「パスワードにはsalt(塩)を振って使いましょう」という文句が覚えやすそうです。

・ストレッチングする

 ストレッチングは関数でハッシュ値にした結果を、さらに関数でハッシュ値にするという手法です。
 何回関数を実行したらいいか?については、多くした方が解析が困難ですが毎回ログイン認証のたびに、
 数十回も関数を実行しているとCPUを多く消費するのでサーバスペックと相談です。

■bcryptを使ってみる

 今回の記事の本題です。Node.jsでbcryptを使ってみます。
 bcryptのページは下記となり、bcryptを使用するだけでsaltを付与することとストレッチングを行えます。
 www.npmjs.com
 Nodeのバージョンによって互換性があるので、今回の記事は下記環境で行います。
 

・環境

 OS: Windows 10 home 64bit
 Node: v10.15.0
 bcrypt: 3.0.3

・bcryptをインストールする

 まずはbcryptをインストールします。

 npm install bcrypt --save

 上記コマンドを実行しても私の環境ではERRが発生インストールできませんでした。
 どうやら依存関係が整っていないらしく、
Installation Instructions · kelektiv/node.bcrypt.js Wiki · GitHub
に従い、管理者権限でpowershellを起動し下記コマンドを実行します。

 npm install --global --production windows-build-tools

 再度bcryptをインストールします。問題なく成功しました。

 npm install bcrypt --save
・bcryptを使ってハッシュ値を作成する

 サンプルのスクリプトを用いて確認します。
 今回は後続ステップでbcryptで生成したハッシュ値を使うので同期メソッドを利用しています。
 非同期が推奨のようですが、DBにハッシュ値にしたパスワードを格納する際は、
 非同期メソッドでハッシュ値を作成したときにコールバックに記載するなどしないと、
 データがなくて登録処理失敗することが予想されます…

/* bcrypt-sample.js */
'use strict'

const bcrypt = require('bcrypt');
const saltRounds = 10; // ストレッチングの回数
const myPlaintextPassword = 's0/\/\P4$$w0rD';
const someOtherPlaintextPassword = 'not_bacon';

// ハッシュ値を生成する
var hash = bcrypt.hashSync(myPlaintextPassword, saltRounds);

console.log("myPlaintextPassword: " + myPlaintextPassword);
console.log("someOtherPlaintextPassword: " + someOtherPlaintextPassword);
console.log("hash: " + hash);

//ハッシュ値を比較する
console.log("compare : '" + myPlaintextPassword + "' with '" + hash + "', result: " + bcrypt.compareSync(myPlaintextPassword, hash));
console.log("compare: '" + someOtherPlaintextPassword + "' with '" + hash + "', result: " + bcrypt.compareSync(someOtherPlaintextPassword, hash));

 上記スクリプトを実行します。

$node bcrypt-sample.js
myPlaintextPassword: s0//P4$$w0rD
someOtherPlaintextPassword: not_bacon
hash: $2b$10$YRZKU2MoISXVNNWSNdVLiuQ4H0cnMpIijbAF99v2Wk907nNBEJMpy
compare : 's0//P4$$w0rD' with '$2b$10$YRZKU2MoISXVNNWSNdVLiuQ4H0cnMpIijbAF99v2Wk907nNBEJMpy', result: true
compare: 'not_bacon' with '$2b$10$YRZKU2MoISXVNNWSNdVLiuQ4H0cnMpIijbAF99v2Wk907nNBEJMpy', result: false

このようにハッシュ値が生成されていることと、比較結果が確認できます。
bcryptで生成されたハッシュ値の文字数は60となり、
'$2b$10$YRZKU2MoISXVNNWSNdVLiuQ4H0cnMpIijbAF99v2Wk907nNBEJMpy'
の見方について先頭から…
 $2b$:暗号化のバージョン
 $10$:ストレッチングの回数
 YRZKU2MoISXVNNWSNdVLiu:salt
 Q4H0cnMpIijbAF99v2Wk907nNBEJMpy:ハッシュ値
となります。

毎回実行するたびにsaltが変更になるため、hashが変更になってしまうので…
DBに格納するときや参照するときは念のため気を付けた方がよさそうです。
 

■終わりに

IDとパスワードでセキュリティを確保しようというのは、難しい時代になってきたと感じました。
Y社は認証技術の黒帯の方もいるぐらいですし、認証技術は日々あれやこれやとユーザを守るために進歩してますね。
自分たちで認証手法を確立する技術力を持っていたらいいですが、それなりのセキュリティを確保したい場合は既存ライブラリを使うのが早そうです。

内容に誤り等ありましたらご指摘お願いします。

以上。