文字列のハッシュ化は、md5(), sha1()を使うのをやめてpassword_hash()を使いましょうという話です。
(ハッシュは文字列を解読しづらくする難読化のこと。)
公式ドキュメントもそう言っているのに気づきました。
(けっこう遅いけど。)
ハッシュって何?
ハッシュは、複雑なルール(アルゴリズム)で意味不明の別の文字列に変換することで、パスワードなど見られるとマズい文字列をデータベースなどへ保存するのに使います。
またキャッシュ(クッキーも)の名前など、ファイル名から何をしているのか悟られたくないものに使います。
データベースなどへパスワードをそのまま保存するのはセキュリティ上よくありません。
(データベースを参照されるともろバレするため。)
そこでハッシュ化した文字列を使います。ハッシュ文字列の復元はほぼ不可能です。パスワードをDBに登録する処理を書いた人でさえできません。
ハッシュ化する関数やクラスなどはあるが復元するものはない。
鍵師のような人が世の中にはいます。それぐらいのレベルじゃないと復元できません。
復元できないパスワードをもつ意味があるのか?
と思うかもしれませんが、ハッシュ化されたパスワードは、復元できなくても認証はできます。処理はこんな感じ。
/**
* パスワード登録
*/
function register_password( $id, $password ) {
$hash = md5( $password );
register_password_db( $id, $hash ); // 勝手に作った仮の関数
}
/**
* パスワード取得
*/
function get_password( $id ) {
return get_password_db( $id ); // 勝手に作った仮の関数
}
/**
* パスワード認証
*/
function auth_password( $id, $password ) {
$password_hash = get_password( $id );
if ( md5( $password ) === $password_hash ) {
return true;
} else {
return false;
}
}
$id = '111';
$password = 'testpass';
// パスワード登録
register_password( $id, $password );
// パスワード認証
if ( auth_password( $id, $password ) ) {
echo 'パスワード認証 OK';
} else {
echo 'パスワードが間違っています。';
}
元データからハッシュ値が作れるので
ハッシュ値で比較すればいいじゃん。
ということです。これまでは。
md5(), sha1()には強い警告が出ている
PHPのハッシュ化でよく使われるのがこれ。
md5()
sha1()
いまでもググればこれらの関数をよく見ますが、その情報は古いです。
公式ドキュメントでも警告しています。理由は、いまのコンピュータの性能では、いとも簡単に高速にハッシュ値から復元するから。
ネックは『常に同じハッシュ値』を作ること。
md5()やsha1()は、元データが同じならハッシュ値もつねに同じです。
$password = 'testpass';
echo md5($password) . PHP_EOL;
echo md5($password) . PHP_EOL . PHP_EOL;
echo sha1($password) . PHP_EOL;
echo sha1($password) . PHP_EOL . PHP_EOL;
179ad45c6ce2cb97cf1029e212046e81
179ad45c6ce2cb97cf1029e212046e81
206c80413b9a96c1312cc346b7d2517b84463edd
206c80413b9a96c1312cc346b7d2517b84463edd
これがパスワードを復元やすくします。ハッシュ値を盗まれたら、大量のパスワードのハッシュ値を作って、それを比較して当てることができるので。
同じハッシュ値を作らない関数
そこで改良したのが
password_hash()
password_hash()は同じパスワードでも、ハッシュ値はちがうものを作ります。
$password = 'testpass';
$hash = password_hash($password, PASSWORD_DEFAULT);
$hash2 = password_hash($password, PASSWORD_DEFAULT);
$hash3 = password_hash($password, PASSWORD_DEFAULT);
echo $hash . PHP_EOL;
echo $hash2 . PHP_EOL;
echo $hash3 . PHP_EOL . PHP_EOL;
$2y$10$nAwvrQrv1PLLEOk3/3DvkerGDXzpB4BL15TnQQmz/BbKlSCMfhh92
$2y$10$2tqA/1/a7S9lAF.W5/NaRuGsu4kvWOINu2MLEMwDHRM4XXkP8bbaG
$2y$10$Rd.iP8CHo6y05oQkTnBSsenN1DrJB4zg2GQI1.hQL8Ng17cC/x6Qe
値がちがうだけじゃなく、作られる文字列も長くなり複雑になっています。
第2パラメータは、PASSWORD_DEFAULTを使うことを強く推奨しています。
『将来、このパラメータ自体無くしたい』ようなことも言うくらいなので。
認証も関数ひとつでかんたん
と思いますが、そこはきちんと関数が用意されています。
password_verify()
$password = 'testpass';
$hash = password_hash($password, PASSWORD_DEFAULT);
$hash2 = password_hash($password, PASSWORD_DEFAULT);
$hash3 = password_hash($password, PASSWORD_DEFAULT);
echo $hash . PHP_EOL;
echo $hash2 . PHP_EOL;
echo $hash3 . PHP_EOL . PHP_EOL;
echo var_dump(password_verify($password, $hash)) ;
echo var_dump(password_verify($password, $hash2));
echo var_dump(password_verify($password, $hash3)) . PHP_EOL;
$password2 = 'testpas';
echo var_dump(password_verify($password2, $hash));
echo var_dump(password_verify($password2, $hash2));
echo var_dump(password_verify($password2, $hash3));
$2y$10$hgUdeKTO0utU2KtrN2snMezeL7qdOCrNYXBNKlobIQNJj8BuAqvP.
$2y$10$VQj3q8wuWm9fj43foWdrtO.v1EXtmh6hdsWSIoyQcP2Sx3rrd7YFq
$2y$10$rwMJhUmmmXh3erFg2ADG/OOBhU76k5bPI/9OrLFkN1RJk2MVLoeBG
bool(true)
bool(true)
bool(true)
bool(false)
bool(false)
bool(false)
ちがうハッシュ値でも認証結果は同じです。
と思いますが、大事なのは
ちがうハッシュ値なのに認証できる処理の複雑さ
md5(), sha1()よりも複雑なアルゴリズムで処理をするので、同じような方法で復元しようとしても、解析はむずかしくなります。
(時間がかかる。)
だから、 password_hash(), password_verify()を使いましょうということですね?
password_hash()のアルゴリズムは、セキュリティ上問題があれば、より複雑にバージョンアップしていくそうです。
その他の関数
他にも、ハッシュの関数が用意されています。
password_get_info() | ハッシュを作った方法の情報を返す |
password_needs_rehash() | ハッシュを作った方法が合っているか確認する |
ハッシュ値を作ったオプションが分からない状態から認証する
ときに使います。
$password = 'testpass';
$hash = password_hash($password, PASSWORD_DEFAULT);
echo $hash . PHP_EOL . PHP_EOL;
$info = password_get_info($hash);
echo var_dump($info) . PHP_EOL . PHP_EOL;
$rehash = password_needs_rehash($hash, PASSWORD_DEFAULT, $info['options']);
echo var_dump($rehash) . PHP_EOL;
$2y$10$XXpJPmmGzQ1CzhkayEz4fe7C1P51q4sMJSWNWh57D8CBeS/XW9ELO
array(3) {
["algo"]=>
int(1)
["algoName"]=>
string(6) "bcrypt"
["options"]=>
array(1) {
["cost"]=>
int(10)
}
}
bool(false)
password_***()の関数は、PHPバージョン5.5(サポート終了)以降で使えます。今は、それより前のバージョンを使うことはあまりないので気にする必要はないでしょう。