PHP 暗号 2016年08月22日 12:20   編集
パスワードをハッシュ化する場合、perlでは、crypt関数を使うと有効な文字列が8文字までという制限が有るため、MD5モジュールを使ってハッシュ化していたが、PHPの場合は逆にcryptを使った方が強度は高いようだ。
ハッシュ化の目的は、サーバー上に保存したパスワードが万一漏洩しても、元のパスワードが知られないようにするためで、この場合復号する必要は無い。ハッシュ化されたパスワードと元のパスワードを照合して、ハッシュ化されたものが元のパスワードから生成されたものであることがわかりさえすればいい。
PHPでmd5()関数を使うと、ハッシュ値と呼ばれる32桁の16進数に変換される。
$str = 'abcd';
$md5 = md5($str);
echo $md5;
実行結果
e2fc714c4727ee9395f324cd2e7f331f
たとえば上記のように「abcd」という文字列をmd5でハッシュ化すると、「e2fc714c4727ee9395f324cd2e7f331f」という文字列が得られる。
「abcd」とハッシュ化された文字列は1対1の対応で、「abcd」を何度md5でハッシュ化しても「e2fc714c4727ee9395f324cd2e7f331f」になる。
このハッシュ値から元の文字列を得る方法はない。つまり「e2fc714c4727ee9395f324cd2e7f331f」から元の「abcd」を得ることはできないということが、秘匿性を守る根拠になっている。
ところが、ハッシュ値のデータベースから元の値を得るモジュールなるものが存在するらしい。 様々な文字列から得られたハッシュ値を多数データベースに保存し、その中に「abcd」をハッシュ化した「e2fc714c4727ee9395f324cd2e7f331f」があれば、それから逆引きして元の文字列が「abcd」であるということがわかるという理屈だ。
もちろん、このデータベースに登録されていないハッシュ値を生成する文字列はわからない。
データベースにどれくらいのハッシュ値が保存されているのかわからないが、よく使われるようなパスワードだとデータベースに存在する確率が高く、その場合短時間で解読(というか、ハッシュ値がデータベースにあるかどうか調べて、対応する文字列を取り出すだけだが)されてしまうおそれがある。

crypt()

そこでcryptだが、たとえば
$str = 'abcd';
$crypt = crypt($str,'');
echo $crypt;
のようにして$cryptを得ると、
$1$Dr3.QB1.$0auGiSkjvstq2ArwbXtkC1
$1$ZN/.uw5.$4SEFH41P0nZI/7wR2zyjZ/
のように、実行するたびに違う値が得られる。

cryptはPHPのバージョンによって動作が若干違うようだ。
crypt($str)のように第2引数のsaltを省略できる場合もあれば、省略するとエラーになる場合もある。
また、出力するハッシュ方式も複数のタイプをサポートしていて、第2引数で指定するsaltの文字列によって変わるようだ。
crypt($str,'')のようにsaltを空白にしても動作するが、この場合$1$ ではじまる 12 文字のsaltが与えられたと判断するようで、この場合はMD5ハッシュを生成する。
指定するsaltによって、より強固なハッシュ値を得ることができるようだが、crypt($str,'')で得られるMD5ハッシュで通常は問題ないだろう。

password_hash()

password_hash() という関数もある。
$str = 'abcd';
$p_hash = password_hash($str, PASSWORD_DEFAULT);
echo $p_hash;
のような感じで使用する。この場合
$2y$10$SQLMx/kW8PjEVot0Chci7.B8zjIAOE2BV7bhnU5DUFTT31U24zQ6i
$2y$10$oQRH8zqUP/m5F9ahdv7iGuET25sCtMbi0JQM6dqzPk22ZSEC1A0oq
のように60桁のハッシュ値を生成する。これもcryptを使った場合と同様に実行するたびに違う値を生成する。
単純に考えてcrypt()の34桁より、60桁の方がより強固なハッシュ値と考えていいだろう。
cryptにしてもpassword_hashにしても、同じパスワードから無数のハッシュ値が生成されるので、1対1で対応するmd5のように、ハッシュ値のデータベースによる逆引きはかなり難しいと考えられる。つまり解読される危険が少ないということだ。
第2引数は整数値で指定するようだが、PASSWORD_DEFAULTにしておけば、将来もっと強力なアルゴリズムが追加されれば、それに変更されるということだ。この第2引数は
PASSWORD_DEFAULT、PASSWORD_BCRYPT、CRYPT_BLOWFISH、整数値の1などが使用できるようだが、今のところどれを指定しても同じ結果が得られるようだ。

password_verify()

照合する場合はpassword_verify()を使用する。
$strがもとのパスワード、$p_hashがpassword_hash() で作成したハッシュ値とすると、
if (password_verify($str,$p_hash)) {
echo "パスワード一致";
} else {
echo "パスワード不一致";
}
で照合できる。
crypt()で生成したハッシュもこのpassword_verify()で照合できるようだ。
(逆にpassword_hash()で生成したハッシュをcrypt()で照合することもできるようだ。)
ハッシュの生成、照合の手軽さ、ハッシュの強度からすると、password_hash()でハッシュ化、password_verify()で照合というのがいいかもしれない。
注意すべきは、password_hash()、password_verify()はPHP5.5で追加された関数らしいので、古いPHPで動かそうとするとエラーになるということ。
以下のように古いPHPの場合はcrypt()を使うように処理した方が汎用性は高い。
$str = 'abcd';
if (function_exists("password_hash")) {
$p_hash = password_hash($str, PASSWORD_DEFAULT);
} else {
$p_hash = crypt($str, '');
}
echo $p_hash;
照合のpassword_verifyも古いPHPではエラーになるので、password_verify()が使えない環境ではcrypt()で照合する処理。
if (function_exists("password_verify")) {
if (password_verify($str,$p_hash)) {
echo "パスワード一致";
} else {
echo "パスワード不一致";
}
} else {
if (crypt($str,$p_hash)) {
echo "パスワード一致";
} else {
echo "パスワード不一致";
}
}

追記

function_exists は環境によってはえらく重くなる。
このため、上記の
if (function_exists("password_verify")) {

if (phpversion() >= 5.5) {
に変更した方が無難なようだ。
counter:6,357