HMACを使った鍵の生成(導出)方法を書いているので、念の為にパスワードのハッシュ化方法について書いておきます。一般にユーザー入力のパスワードをアプリケーションデータベース等に保存する場合、HMACやHKDFを使わずに、password_hash()を使うべきです。
参考:
password_hashとは
パスワードをアプリケーションに保存する場合、パスワードが管理者や開発者、攻撃者によって解析/窃取されるリスクを考慮する必要があります。平文のテキストでパスワードを保存した場合、簡単に盗まれてしまいます。
password_hash関数はcrypt関数のラッパー関数です。password_hash関数でできることはcrypt関数できます。password_hash関数が存在する理由は、crypt関数の仕様は使い易いとは言えず、間違った使い方をすると安全ではないからです。
string crypt ( string $str [, string $salt ] )
crypt関数を使った場合、ハッシュ化を行う場合の動作を$saltに”テキスト”として指定しなければなりません。crypt関数を安全に利用するには、利用するハッシュ、ハッシュ関数の適用回数などを$saltの値に設定する必要があります。
以下はpassword_hash関数の実行例です。
$ php -r 'var_dump(password_hash("password", PASSWORD_DEFAULT));' string(60) "$2y$10$XjfAtRDfO32a2JaSwcCvZeS.NZLn5H8FloFhGlcFm7XN44pj15.Jm"
- “$2y$”がハッシュ関数の指定(この場合、Blowfish)
- “$10$”の10がハッシュ関数の実行回数の指定(この場合、Blowfishなので2^10 = 1024回)
- “XjfAtRDfO32a2JaSwcCvZeS.NZLn5H8FloFhGlcFm7XN44pj15.Jm”が自動的に付与されたsaltとsaltとパスワードから計算したハッシュ値
使用したハッシュ関数、適用回数、saltもハッシュ値もすべて記述されています。ブルートフォース攻撃実施に十分な条件です。
それでもpassword_hash(crypt)の方が安全と言われる理由は、多量のCPU計算量を必要とさせることによりブルートフォース攻撃を”緩和”するからです。
緩和しているだけで、上記の実行例のような”password”といった、使ってはならない単純なパスワードの場合は簡単に解析できます。 何千、何万ユーザーのパスワードを解析するには膨大すぎる時間が必要ですが、かなり複雑なパスワードであっても特定ユーザーのパスワード解析なら十分に可能です。
詳しいpassword_hash関数の説明
パスワードなので、ユーザーが入力した文字列とシステムが保存している文字列(データ)が一致しているか確認する必要があります。何らかの方法で照合できるようにしなければなりません。
このためpassword_hash関数(crypt関数)はユーザーが入力したパスワードをアプリケーションと”同じストレージ”にできるだけ安全な形で保存できるようにハッシュ化します。
string password_hash ( string $password , integer $algo [, array $options ] )
- 暗号学的なハッシュ関数を使う(現在のデフォルトは計算量が多いBlowfish)
- 暗号学的に安全なsaltを自動設定する(システムCSPRNGからsalt値を取得する)
- ハッシュ関数に対して適当な適用回数(rounds)を自動的に設定する(ブルートフォース攻撃対策)
- 自動的に利用しているハッシュ関数を検出し、パスワードに一致/不一致を検証する(password_verify関数)
基本的な使い方は、$passwordにユーザー入力のパスワード、$algoにはPASSWORD_DEFAULT定数を設定して戻り値をユーザーのパスワードとして保存します。ログイン時の検証にはpassword_verify関数を使います。
<?php if (password_verify($user_entered_password, $password_hash) == TRUE) { // Password is good } else { // Password is wrong }
password_hash関数は$algoで指定されたアルゴリズムを使い、適当なroundsでパスワードをハッシュ化します。rounds(cost)は$optionsで変更できます。
password_hash関数は暗号学的なハッシュ関数で複数回ハッシュ化を行い、攻撃者がパスワードのハッシュ化文字列を取得した場合でも、パスワード解析に多くのコスト(CPU時間)が必要となるようになっています。
- crypt()はSHA256やSHA512をサポートしているが、password_hash()はBlowfishしかサポートしていない。これはSHA256/512に比べ、Blowfishハッシュの計算には多くのCPUが必要であることが理由。
- Blowfish(PASSWORD_BCRYPT)のデフォルトのrounds(cost)は10、つまり2^10回ハッシュ化を行う。
roundsは比較的少な目に設定されています。計算量が多過ぎるとDoS攻撃の原因になるからです。
小型PC(IoT)などでは10(2^10)のrounds(cost)でも結構厳しいでしょう。現在の高速なCPUを対象にするなら11〜14程度にする方が良いと思います。アプリケーションの重要性に応じて設定すると良いでしょう。
DoS攻撃だけでなく、単純なパスワード推測攻撃に対応できるよう、パスワードの検証回数が制限できるようにする必要があります。
password_hash関数では利用できませんが、crypt関数でSHA256を使う場合、少なくとも1万回以上のroundsを設定する方が良い1です。Blowfish(BCRYPT)とSHA256/512ではrounds(cost)の設定が2のべき乗と実際の処理回数と異なっている点に注意してください。詳しくはcrypt関数のマニュアルを参照してください。
HMAC/HKDFの安全性
ユーザーが入力したパスワードを直接暗号鍵に利用するのは好ましくありません。ユーザー入力のパスワードは極端に弱い鍵になるからです。
HMAC(hash_hmac関数)HKDF(hash_hkdf関数)を使って安全な鍵を導出可能ですが、password_hash関数とは安全に利用する前提条件が異なります。
password_hash関数
- 目的:ハッシュ関数もsalt(第二の鍵)もハッシュ値も分かっているが、鍵(パスワード)の解析を困難にさせる。
- 安全性:大量のCPU計算量でブルートフォース攻撃を緩和する。簡単な鍵(パスワード)なら簡単に解析できる。
hash_hmac関数
- 目的:暗号学的なハッシュ関数に多少の問題があっても、暗号学的に安全なハッシュ値を生成する。
- 安全性:安全性は入力データ(このエントリの場合パスワード)かsalt(第二の鍵)のどちらかが暗号学的に安全かつ秘密であることが条件。入力データが弱いパスワードでも強い秘密のsalt(第二の鍵)により、出力されたハッシュ値の安全性は暗号学的に保証される。
hash_hmac関数には導出した鍵の有効期限をパラメーターとして保持できない欠点があります。この欠点を解消できるhash_hkdf関数があります。
hash_hkdf関数
- 目的:暗号学的に安全な導出鍵を生成する。
- 安全性:hash_hmac()と変わらないが、info引数を利用し鍵の有効期限や適用範囲などを設定できる。
hash_hmac()/hash_hkdf()共にパスワード(秘密情報)エントロピーのストレッチ(伸長)を行いません。このため、saltを使っていてもストレッチを行うhash_password()やPBKDF2に比べるとパスワード保存には向かない脆弱な方法と言えます。
hash_hmac/hash_hkdfを利用する場合、パスワードとsaltを別々に安全に(物理的に別サーバーなど)保存できる場合は利用しても安全性を保てます。saltを含め一つのデータベースに保存するより、別々に保存するとそれなりの安全性を期待できます。
PBKDF2
PBKDF2はNISTが作ったパスワードハッシュ化の標準です。PHPにもPBKDF2関数、hash_pbkdf2()があります。仕様などでPBKDF2が要求されている場合、こちらを使います。hash_pbkdf2()も安全にパスワードを保存できます。
string hash_pbkdf2 ( string $algo , string $password , string $salt , int $iterations [, int $length = 0 [, bool $raw_output = false ]] )
基本的にハッシュ関数は強いハッシュ関数が良いです。SHA3-512が良いでしょう。以下の様に使います。
<?php $password_hash = hash_pbkdf2('SHA3-512', $password, random_bytes(64), 10000);
$saltは必ずrandom_bytes()で64バイト以上の値を設定します。$iterationsには1万回以上を設定すると良いでしょう。$iterationsを増やせば増やすほど総当たり攻撃によるパスワード解析に対して強くなります。しかし、増やしすぎるとDoSの原因にもなるのでバランスを考えて大きな値を設定します。
まとめ
password_hash関数とhash_hmac関数は作られた目的も、それなりに安全に動作する条件も異ります。
このエントリでは一つ一つの基礎的な概念や仕様を丁寧に説明していないです。理解り易く説明できていないですが、利用目的に合わせて適切な関数を利用しましょう。
hash_hmac関数を利用する場合でも、”何かの目的、例えば暗号化、の為にユーザー入力のパスワード”をデータベースに保存する必要があると思います。その場合、hash_password関数を使うとより安全です。
password_hash関数は73文字以上の文字を切り捨てます。長いパスワードを使う場合、SHA512などでハッシュ化してから使う必要があります。
パスワードハッシュとsaltを一緒に保存する場合はpassword_hash関数を使いパスワードをハッシュ化すると良いです。しかし、saltを別システムに安全に保存できる場合は、HMAC/HKDFを使った方がより安全です。攻撃者はシステムに保存されたハッシュ値だけを盗んでも、saltを知らない限りブルートフォース攻撃によるパスワード解析が行えなからです。
参考:HMACハッシュ関数使い方まとめ
参考:SSLに脆弱性が無くても、会社などでは解読しています。こういった場合にも備える方法
- パスワード管理ツールなどではCPU計算量を増やすためにSHA2ハッシュ関数を10万回単位で繰り返し適用しています。 ↩