Bcryptを用いてパスワードをハッシュ化する (Node.js)

ログイン機能のあるアプリケーションを開発する場合、(大体の場合は)ユーザーにパスワードを設定してもらうことになります。当然ですがそれらのパスワードを平文でデータベースに保存したりするのはセキュリティ的にご法度で、ハッシュ化して保存をする必要があります。

この記事では、bcrypt というパッケージを用いて、Node.jsでパスワードをハッシュ化する方法・そしてそれを用いてパスワードをチェックする方法を紹介します。ちなみに、bcryptはハッシュ化アルゴリズムの名前でもありパッケージの名前でもあります。以下混同して使用しています。

準備

まずはbcryptをインストールします。TypeScriptの型定義ファイルももちろんあります。

$ npm install bcrypt --save
$ npm install @types/bcrypt --save-dev # TypeScriptを利用している場合

ちなみに執筆時では bcrypt@3.0.5 が入りました。

ハッシュ化

まずは平文のパスワードをハッシュ化します。会員登録時やパスワード変更時に行う処理ですね。

以下のように、パスワードを「ラウンド数」とともに bcrypt.hash 関数に渡します。

ラウンド数を大きくすればするほど安全なハッシュを生成することができますが、計算に要する時間が長くなります(ラウンド数が1増えると時間は2倍ほどになるそうです)。こちらによると、ラウンド数10でのハッシュ化には2.0GHzのCPUで0.1秒ほどかかるようです。

ここではラウンド数を10に設定しています。通常はこれくらいで(もう少し小さくても?)大丈夫そうです。

const bcrypt = require('bcrypt');

const saltRounds = 10;

bcrypt.hash(password, rounds)
  .then((hashedPassword) => {
    // ...
  });

チェック

平文のパスワード(password)とハッシュ化されたパスワード(hashedPassword)を比較し、その平文のパスワードが正しいかどうかをチェックします。ログイン時の処理ですね。正しければisCorrectPasswordtrueに、そうでなければfalseになります。

bcrypt.compare(password, hashedPassword)
  .then((isCorrectPassword) => {
    // ...
  });

おまけ

  • bcryptの計算の概要について大雑把に説明します。まずランダムでソルトを作成しています。そして、平文パスワードとソルトを組み合わせてハッシュ化し、そのハッシュとソルトを組み合わせてハッシュ化し、そのハッシュとソルトを組み合わせてハッシュ化し...というのを何回も繰り返しているようです。その回数を調整しているのがラウンド数というわけです。
  • 上の例でのhashedPasswordには、ラウンド数やソルトの情報も含まれています。compare関数は「ラウンド数やソルトの情報を抜き出し、それをもとに平文パスワードをソルトともに指定回数ハッシュ化し、比較する」ということをやってます。
  • 上の例の通り、bcryptは、コスト(ラウンド数)を設定できるようになっています。これにより、コンピューターの計算能力が伸びて攻撃側の能力が上がったとしても(ラウンド数を上げることで)同じアルゴリズムを利用し続けることができるというメリットがあります。
  • bcryptには、hashSync 関数や compareSync 関数などの同期的な関数も存在します。しかし、簡単なスクリプト等以外には使用はするべきではありません。上にも書きましたが、これらの関数は計算量の大きい処理をするので、ある程度時間がかかります。その間イベントループがブロックされてしまい、他の処理が(ほとんど)できなくなります。サーバーアプリケーションの場合はリクエストに対する処理ができなくなってしまいます。ちなみに、hash 関数や compare 関数を呼び出した際はスレッドプールを利用して(メインスレッド外で)処理をするので、イベントループのブロックはおきません。そして、複数のCPUコアを有効活用できます。