PBKDF2を用いたパスワード暗号化機能サンプル

本サンプルは、 PBKDF2 を使用してパスワードの暗号化を行う実装サンプルである。

提供パッケージ

本サンプルは、以下のパッケージで提供される。

please.change.me.common.authentication

概要

PBKDF2を使用して、ソルト付加およびストレッチングを行ってパスワードを暗号化する機能の実装サンプルを提供する。

本サンプルは、 Nablarch実装例集 内で使用することを想定している。

要求

実装済み

  • 複数ユーザが同一のパスワードを使用している場合にも、暗号化されたパスワードを異なる値とすることができる。
  • 暗号化前の文字列に十分な長さを持たせ、レインボーテーブルによるパスワード解読への対策を行うことができる。
  • 暗号化パスワードを一度計算するのに必要な時間を変更することで、総当りでのパスワード解読への対策を行うことができる。

未検討

  • ユーザごとにランダムなソルトを付加することができる。
  • ソルトをデータベース外のセキュアなストレージに保管しておくことができる。

パスワード暗号化機能の詳細

本機能では、鍵導出関数のひとつである PBKDF2 を使用して、パスワードを暗号化する。 暗号化されたパスワードは、Base64エンコードされた文字列として返却する。

本機能では、ソルトとして「システム共通の固定値」と「ユーザID」を連結したバイト列を使用しており、 異なるユーザが同一のパスワードを使用した場合にも、暗号化されたパスワードは異なる値となる。

また、下記で説明する fixedSalt プロパティで十分な長さのソルトを指定することでレインボーテーブル対策を行うことができ、 iterationCount プロパティでストレッチング回数を指定することで、総当り攻撃対策を行うことができる。

ストレッチング回数の検討方法の参考情報は、 ストレッチング回数の設定値について を参照。

設定方法

本機能の設定方法について解説する。

<!-- パスワード暗号化モジュールの設定 -->
<component name="passwordEncryptor"
           class="please.change.me.common.authentication.PBKDF2PasswordEncryptor">

  <!-- システム共通でソルトに利用する固定文字列を設定する。20バイト以上の文字列を設定しておく。 -->
  <property name="fixedSalt" value=" !!! please.change.me !!! TODO: MUST CHANGE THIS VALUE." />

  <!-- SHA-256ハッシュ計算の10000倍程度の計算時間となるように、ストレッチング回数を設定する。 -->
  <property name="iterationCount" value="3966" />

  <!-- 暗号化されたパスワードの長さ(ビット数)を設定する。 -->
  <property name="keyLength" value="256" />
</component>

プロパティの説明を下記に示す。

property名 設定内容
fixedSalt(必須)

システム共通でソルトに利用する固定文字列。実際のソルトは、この文字列にユーザIDを連結したバイト列となる。

重要

本設定値は、パスワードの暗号強度に関わる値となる。設定する値が短すぎる場合には、 レインボーテーブルを使用したパスワードの解読に対して脆弱性が残ってしまう。

ソルトとしてはユーザIDを連結するため、この文字列とユーザIDを連結したバイト列が 必ず20バイト以上 [1] の長さとなることを保証すること。(本設定値のみで20バイトを確保することを推奨する。)

iterationCount

パスワード暗号化のストレッチング回数。デフォルト値は3966。 [2]

ストレッチング回数は、一般的に数千回~数万回が推奨されていることと、システムに対する負荷を考慮し、 SHA-256などのハッシュ化関数での計算時間の10000倍程度の計算時間になるように設定すること。

ストレッチング回数の検討方法についての参考情報を、 ストレッチング回数の設定値について に記載している。

ちなみに

ストレッチング処理は、CPU負荷の高い処理となる。

PCIDSSに準拠するシステムでなく、特別なセキュリティが必要なければ 1 を指定すればよい。

keyLength

暗号化されたパスワードの長さ(ビット数)。デフォルト値は256。

内部で使用されているハッシュ関数がSHA-1であるため、設定値には160以上の値を設定すること。

本機能を使用して生成される文字列の長さは、ここで指定した長さのバイト列をBase64で エンコードした長さになる。

[1]2014年1月時点で、14文字以上の文字列に対応したレインボーテーブルの販売が確認されているため、ここでは20文字以上を推奨している。 プロジェクトでの使用に当たっては、必ず最新の情報を確認し、十分であると想定できるソルト長を設定すること。
[2]3966という数字に特に意味はないが、目的を果たせるストレッチング回数であれば、推測が容易となるキリのいい回数を指定するよりも、 推測が容易でない値を設定することで、パスワードを解読される脅威が緩和できると判断して設定している。

ストレッチング回数の設定値について

参考として、本サンプル実装におけるストレッチング回数のデフォルト値をどのように検討したかについて記載する。

基本的な方針として、以下の情報を元にストレッチング回数を決定した。

  1. パスワードがSHA-256・ストレッチングなしでハッシュ化されている場合に、何秒で総当りが完了するか。
  2. 総当り完了までにどの程度の時間がかかるようにするかの目標値を定め、 パスワードを1回ハッシュ化する時間が、SHA-256の場合の何倍となれば目標値を達成できるか。

上記の方針で検討するために、以下の情報を収集した。

1秒間のハッシュ値計算回数
2013年11月時点では、1秒間に100,000,000,000回のSHA-256を計算できるサーバが販売されている。
パスワード強度
「英数字混在8文字以上」のパスワードを強制する場合、総当り攻撃の完了には62^8回の計算が必要。
総当り攻撃完了までにかかる時間の目標値
1年間

上記の情報から、PBKDF2での1回のハッシュ値の計算時間が、SHA-256での1回の計算時間の何倍になるように設定するべきかを計算すると、 下記のようになる。

  1. SHA-256・ストレッチングなしでパスワードの総当りが完了する時間

    (62^8) / (10^11) ~= 2183 (s)
    
  2. 上記の時間を目標値まで伸ばすために、パスワードのハッシュ値計算時間をSHA-256の場合の何倍とすればよいか

    (60*60*24*365) / ((62^8) / (10^11)) ~= 14444
    

この値から、PBKDF2での1回の計算時間がSHA-256での計算時間のおよそ15000倍以上になるように iterationCount を設定すればよいことが分かる。

開発用PC(CPU: Intel(R) Core(TM) i7-4770 3.40GHz)での実測結果によると、 iterationCount をおよそ3500回~4000回程度とすると、 PBKDF2の計算時間はSHA-256の計算時間の15000倍程度になり、総当り攻撃の完了に1年間かかるようにできるということが分かった。

また、上記PCでの実測では、 iterationCount を4000回としたときのPBKDF2の1回の計算時間は、15ms~20ms程度となった。 この値は、1秒程度のレスポンスタイムが想定されるログイン処理などにおいてはボトルネックになる数値ではないと判断し、 デフォルト値として採用した。

ただし、PBKDF2の暗号化処理を実行している間は、同処理がCPUをほぼ占有する。 実際に稼動する環境で暗号化処理がCPUを占有する時間が、許容される時間に収まるかどうかについても必ず検証を行うこと。