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