この節では、なぜハッシュ関数を使ってパスワードを守るのかについての理由と、 ハッシュ処理を効率的に行う方法について説明します。
パスワードのハッシュは、最も基本的なセキュリティ要件のひとつです。 ユーザーからパスワードを受け取るアプリケーションを設計するときには必ず考慮しなければなりません。 ハッシュしなければ、パスワードを格納したデータベースが攻撃を受けたときにパスワードを盗まれてしまいます。 それは即時にアプリケーションが乗っ取られることにつながるし、 もしそのユーザーが他のサービスでも同じアカウント・同じパスワードを使っていればさらに被害が大きくなります。
ユーザーのパスワードにハッシュアルゴリズムを適用してからデータベースに格納しておくと、 攻撃者が元のパスワードを知ることが難しくなります。 とはいえ、パスワードのハッシュ結果との比較は可能です。
しかし、ここで注意すべき点は、パスワードのハッシュ処理はあくまでもデータベースへの不正アクセスからの保護にすぎず、 アプリケーション自体に不正なコードを注入される攻撃からは守れないということです。
MD5 や SHA1 そして SHA256 といったハッシュアルゴリズムは、 高速かつ効率的なハッシュ処理のために設計されたものです。 最近のテクノロジーやハードウェア性能をもってすれば、 これらのアルゴリズムの出力をブルートフォースで(力ずくで)調べて元の入力を得るのはたやすいことです。
最近のコンピュータではハッシュアルゴリズムを高速に「逆算」できるので、 セキュリティ技術者の多くはこれらの関数をパスワードのハッシュに使わないよう強く推奨しています。
パスワードをハッシュするときに検討すべき重要な二点は、 その計算量とソルトです。 ハッシュアルゴリズムの計算コストが増えれば増えるほど、 ブルートフォースによる出力の解析に時間を要するようになります。
PHP には ネイティブのパスワードハッシュ API が用意されており、これを使えば ハッシュの計算 や パスワードの検証 を安全に行えます。
それ以外には、crypt() 関数を使う方法もあります。 この関数は、いくつかのハッシュアルゴリズムに対応しています。 この関数を使うときには、指定したアルゴリズムが使えるかどうかを気にする必要はありません。 各アルゴリズムが PHP の内部でネイティブに実装されているので、 ご利用の OS でサポートしていないアルゴリズムでも使うことができます。
パスワードをハッシュするときのおすすめのアルゴリズムは Blowfish です。 パスワードハッシュ API でも、このアルゴリズムをデフォルトで使っています。 というのも、このアルゴリズムは MD5 や SHA1 と比較して計算コストが高いにもかかわらず、スケーラブルだからです。
crypt() でパスワードを検証する場合は、タイミング攻撃に注意が必要です。 タイミング攻撃を避けるため、処理時間が一定な文字列比較処理を使うようにしましょう。 PHP の == 演算子や === 演算子 も、そして strcmp() も、文字列比較の処理時間が一定ではありません。 その点 password_verify() はこの問題を気にしなくて済むので、 可能な限り ネイティブのパスワードハッシュ API を使うようにしましょう。
暗号理論におけるソルトとは、ハッシュ処理の際に追加するデータのことです。 事前に計算済みのハッシュとその元入力の対応表 (レインボーテーブル) で出力を解析される可能性を減らすために利用します。
端的に言うと、ソルトとはちょっとした追加データです。 これをつけるだけで、ハッシュをクラックするのが劇的に難しくなります。 事前に計算済みのハッシュとその元入力を大量にまとめた表が、オンラインで多数公開されています。 ソルトを使えば、そのハッシュ値がこれらの表に含まれている可能性を大きく減らすことができます。
password_hash() は、ソルトを指定しなかった場合にはランダムなソルトを作ります。 一般に、これがいちばんお手軽で安全なアプローチでしょう。
password_hash() や crypt() を使った場合、戻り値であるパスワードハッシュの中にソルトが含まれています。 このソルトは、そのままの形式でデータベースに格納する必要があります。 というのも、利用したハッシュ関数の情報がそこに含まれており、それを直接 password_verify() や crypt() に渡せばパスワードの検証ができるからです。
crypt() や password_hash() の戻り値の書式を次の図に示します。 このように、使ったアルゴリズムや検証時に必要なソフトに関する情報もすべて含まれています。