安全なハッシュ関数とpassword_hash()

小山哲志

PHP 5.5.0から導入されたpassword_hash()関数について、エンジニアとしての視点から解説します。ハッシュ関数は、入力されたパスワードのデータを安全に保存するために用いられますが、その安全性は時代とともに変動します。SHA-1など昔は安全とされたハッシュ関数が現在では脆弱になり、新しいアルゴリズムへと更新されていく必要があります。password_hash()関数は、このような変化に対応するために作られ、アルゴリズムの変更に影響を受けずにユーザーデータを保護します。記事ではこの関数の仕組みを詳しく紹介します。

今回よりここで連載をさせていただくことになった小山 (koyhoge)です。フリーランスでエンジニアをしておりまして、他にニフティクラウド mobile backend (NCMB) という mBaaS サービスのエバンジェリストもやっているので、それがきっかけでここに書かせてもらうことになりました。

さて第1回目は、PHP 5.5.0 から新たに導入された password_hash() 関数について深掘りしていきたいと思います。

ハッシュ関数とは

このテーマを取り上げるきっかけは、今年の1月31日に開催されたPHP勉強会@東京での、長谷川智希さんの発表です。


ハッシュと暗号は違うぞ! / Do not confuse hashes and ciphers.

ハッシュ関数は以下の特徴を持ちます。

  • ある値に処理を行った際に、もとの値とは関係く見える別の値に変換される。
  • 変換後の値が同じなのに、そっくりだが実は異なるという変換前の値を得ることは不可能。
  • 変換後の値から変換前の値を推測することは極めて難しい。

これらの特性のため、ユーザが入力されたパスワードをDBなどに保存するためによく使用されます。ユーザから入力された文字列は、ハッシュ関数によって変換されてDBに格納され、しかも変換後の文字列から入力されたパスワードを推測するのは困難なので、仮にDBの内容が流出してもパスワードそのものは守られるという仕組みです。

先日 2月24日に、ハッシュ関数の一種である「SHA-1」が破られたという発表がありました。

Webブラウザのセキュリティ対策など幅広い用途に使われてきたハッシュアルゴリズムの「SHA-1」について、米Googleは2月23日、理論上の可能性が指摘されていたSHA-1衝突を初めて成功させたと発表した。これでSHA-256やSHA-3のような、安全な暗号ハッシュへの移行を急ぐ必要性がこれまで以上に高まったと強調している。

「SHA-1衝突攻撃がついに現実に、Google発表 90日後にコード公開」
http://www.itmedia.co.jp/enterprise/articles/1702/24/news067.html

これはSHA-1を用いてPDFを変換した値が、よく似ているが異なる2つのファイルで同一にする手法が発見されたということです。データが改竄されていないことの確認のために、元データのハッシュ値を公開するという方法はよく行われていますが、そのハッシュ関数がSHA-1の場合は、もはや改竄されていないことは保証できないことを意味します。

とはいえSHA-1はすでに様々な問題が指摘されており、ハッシュアルゴリズムとしてはすでに使用中止が提言されています。これらを改良したSHA-2などが、より安全なハッシュ関数としてすでに使われています。

大事なのは、何が安全なハッシュ関数なのかという点は、時代とともに変化していくということです。現在のSHA-1が発表されたのは1995年ですが、当時の計算速度では十分に安全とされたので広く使用されました。その後計算速度はどんどん上がり、SHA-1変換前の値を効率的に求める手法が発見されたこともあり、現在ではSHA-1は安全なハッシュ関数とは見なされなくなっています。

現在はSHA-1の移行先として、SHA-2のバリエーションの一つであるSHA-256が使われ始めています。しかしこれも現在の計算速度と、今のところ攻撃手法が見つかっていないということでとりあえず安全だろうと見なされているにすぎません。

password_hash() はなぜ生まれたか

このように時代とともに変化する「安全なハッシュ関数」を実現するにはどうしたら良いでしょうか。PHPでハッシュ関数を用いるには、大昔では

$out = sha1($input);

$out = md5($input);

などのようにアルゴリズム名の関数を直接呼び出していました。これはさすがにアルゴリズムを変更したいと思った時に不便だということで、PHP-5.1.2 から hash() 関数が作られました。hash() 関数は

$out = hash(‘sha1’, $input);

とハッシュアルゴリズムを文字列として指定する汎用関数になりました。これならアルゴリズムを設定値として外部に持たせることができます。

ただその場合の問題は、ハッシュとして変換された結果に後方互換性がないことです。SHA-1で変換した値とSHA-256で変換した値は当然ながら異なります。つまりパスワードをSHA-1変換した値としてDBに保持してあった場合、ハッシュアルゴリズムをSHA-256に変更する際にはDBの中の値をすべてSHA-256で変換し直さなければいけません。これをサービスを動かしながら行うのは、間違いが起こりやすく大変な労力です。

その問題を解決するために、PHP-5.5.0/PHP-7.0 からpassword_hash()関数が新たに作られました。以下のように使用します。

// ハッシュ値生成
$out = password_hash($input, PASSWORD_DEFAULT);

// 登録されているハッシュと入力が合っているかチェックする
$valid = password_verify($input, $out);

入力が正しいかどうかの比較には、文字列そのものの比較ではなく password_verfy() という専用の関数を使います。その理由は後述します。

password_hash() に渡すアルゴリズムには、通常 PASSWORD_DEFAULT を指定します。これはそのPHPがリリースされた時点で十分に安全なハッシュアルゴリズムが選択されるという意味です。現在リリースされているPHPでは内部的にはPASSWORD_BCRYPT と同一で、Blowfishアルゴリズムを使用します。将来的にはその意味するところが変わる可能性は大いにあります。

どうやって互換性を保っているのか

password_hash()関数が返す変換後の文字列は、例えば以下の様なものです。

$2y$10$vjrTmLpZIMYAvEbalTg00uMyJs8W8Ij4maSGwvBlveyn6XXT6rSZO

文字列の先頭に「$2y$10$」といういかにも意味がありそうなものが付いています。実際にこれには意味があって、

$ハッシュアルゴリズムを表す文字列$コスト$

を表しています。この例の場合の「2y」はBlowfishをハッシュアルゴリズムとして使用したことを意味しています。続く「$10$」の10はBlowfishアルゴリズムで使用される「コスト」を意味していて反復回数の計算に用いられます。

このようにpassword_hash()が出力する文字列には、

  • どのアルゴリズムを用いて
  • 何回反復して計算して
  • ソルトにはどういう文字列を用いたか(後述)

がすべて含まれていますので、たとえ標準となるハッシュアルゴリズムが変更になったとしても、以前に保存されたものを正しく扱うことが可能なのです。そのためには、出力される文字列を単に比較する従来の方法ではダメで、どんな状態で保存されたのかを加味しながらチェックすることが必須となります。これが password_verify() 関数が生まれた理由です。

ソルトについて

どのようなハッシュアルゴリズムを用いていたとしても、ある文字の入力から出力される値は常に同じです。そのため、パスワードによく使われるであろう一般的な単語を組み合わせた、いわゆる「辞書攻撃」で使用される文字列のハッシュ値を、あらかじめ計算して用意しておく手法があります。さらには指定した長さの文字列の組み合わせすべてを計算して保持しておくレインボーテーブルという手法もあります。もし何らかの原因で、DBに保存されているハッシュ値が漏洩した場合に、レインボーテーブルと比較することでオリジナルのパスワード文字列を割り出すことが可能になってしまうわけです。

これを回避するために、パスワードのハッシュを求める場合は、元のパスワードに対して意味のない文字列を追加してから処理を行うことが一般に行われています。この追加する文字列のことを「ソルト」といいます。

ソルトは乱雑な文字列なので、それを追加した文字列全体に対しては辞書攻撃は無効になります。またソルトを十分な長さにすれば、パスワードの文字列が短くてもレインボーテーブルの対象にはなりにくくなります。

password_hash() 関数は、オプションで自分で用意したソルトを指定することも可能ですが、現在では非推奨で password_hash() 関数が内部で自動生成するソルトを使うことが推奨されています。

もし password_hash() の値が漏洩した場合、ハッシュアルゴリズムとソルトは漏洩者に分かってしまいますが、総当り攻撃をするにはゼロから計算をやり直さなければいけないので、出来合いのレインボーテーブルは役に立たない点は重要なポイントです。

おわりに

ユーザが入力するパスワードをどのように保存するかは、ウェブサービスを作る上での古くからの課題でした。ハッシュを用いるという手法は一般に広まってきているように思いますが、より安全なハッシュを用いるという視点がこれからは求められています。

世界最高速クラスの仮想マシン「KUSANAGI」は、WordPress専用ではなく一般的なPHP環境を高速化できます。PHPを使用するサービス開発者の方々の選択肢としてご考慮いただければ幸いです。

 

画像参照URL:https://www.flickr.com/photos/143601516@N03/29723649810/

作者:Blogtrepreneur

関連記事

Webサイト運用の課題解決事例100選 プレゼント

Webサイト運用の課題を弊社プロダクトで解決したお客様にインタビュー取材を行い、100の事例を108ページに及ぶ事例集としてまとめました。

・100事例のWebサイト運用の課題と解決手法、解決後の直接、間接的効果がわかる

・情報通信、 IT、金融、メディア、官公庁、学校などの業種ごとに事例を確認できる

・特集では1社の事例を3ページに渡り背景からシステム構成まで詳解