password_hashとpassword_verifyで安全にパスワードを扱う
password_hashとpassword_verifyは、パスワードを安全にハッシュ化・照合するための関数です。ソルトを自動生成し、平文や高速ハッシュを避けて安全にパスワードを扱えます。
平文もMD5も論外
パスワードを平文で保存するのはもちろん論外ですが、md5 や sha1 でハッシュ化しているコードもまだ見かけますね。これらは高速に計算できるぶん総当たりに弱く、パスワード用途にはまったく向きません。PHP には password_hash という専用関数があるので、素直にこれを使うのが正解だと思います。ソルトも内部で自動生成してくれるので、自前でソルトを混ぜる必要もありません。
// 登録時
$hash = password_hash($plainPassword, PASSWORD_DEFAULT);
// $hash をDBに保存する。平文は絶対に残さない
// $2y$10$... のような文字列。ソルトもコストも含まれている
PASSWORD_DEFAULT は現時点で bcrypt を指し、将来 PHP のバージョンが上がるとより強いアルゴリズムに切り替わる可能性があります。特別な理由がなければこれを選んでおけばいいと思います。
検証はpassword_verifyで
ログイン時の照合は password_verify に任せます。ハッシュにはソルトもコストも埋め込まれているので、こちらで復元する必要はありません。自分で入力値をハッシュ化して == で比べる、といったことはしないほうがいいですね。
// ログイン時
$hash = $repo->findHashByEmail($email); // DBから取り出す
if ($hash !== null && password_verify($input, $hash)) {
// 認証成功
} else {
// 失敗。理由は詳しく返さない
}
argon2とrehashの運用
より新しいアルゴリズムを使いたいなら PASSWORD_ARGON2ID も選べます。メモリ負荷を上げられるので、環境が対応しているなら候補になりますね。そして地味に大事なのが password_needs_rehash です。コストやアルゴリズムを引き上げたとき、ログイン成功のタイミングで古いハッシュを黙って作り直せます。これを仕込んでおくと、利用者に負担をかけずに強度を上げていけるので、入れておく価値があると思います。
if (password_verify($input, $hash)) {
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$newHash = password_hash($input, PASSWORD_DEFAULT);
$repo->updateHash($email, $newHash);
}
// ログイン処理へ
}
まとめ
パスワードは password_hash で保存し、password_verify で照合する、これが基本にして最善だと思います。平文や高速ハッシュは使わない、ソルトは任せる、そして password_needs_rehash で強度を少しずつ上げられる運用にしておく。この形にしておけば、パスワード周りの事故はかなり減らせるという気がします。
よくある質問
Q. md5やsha1でパスワードをハッシュ化してはだめですか。
A. だめです。高速に計算できるぶん総当たりに弱く、パスワード用途には向きません。password_hashを使います。
Q. ソルトは自分で用意する必要がありますか。
A. 不要です。password_hashが内部で自動生成し、ハッシュ文字列に含めてくれます。
Q. 後からハッシュの強度を上げられますか。
A. password_needs_rehash を使うと、ログイン成功時に古いハッシュを作り直せます。利用者に負担をかけず強度を上げられます。