密碼儲存安全是 Web 開發底線。本文梳理演進歷程:MD5/SHA1 (已淘汰) → SHA256/512 + Salt → Bcrypt/crypt → PHP 5.5+ Password Hashing API (推薦標準),並詳細說明 password_hash()、password_verify()、password_needs_rehash()、password_get_info() 四大函數用法與遷移策略。
| 方法 | 狀態 | 問題 |
|---|---|---|
| MD5 / SHA1 | ❌ 禁用 | 速度極快、彩虹表豐富、無 Salt 易碰撞 |
| SHA256/512 + Salt | ⚠️ 不推薦 | 仍屬快速雜湊,GPU 可秒級暴力破解 |
| Bcrypt (crypt + Blowfish) | ✅ 可接受 | 內建 Salt、可調 Cost (工作因子)、但 API 繁瑣 |
| Argon2id (PHP 7.2+) | ✅ 推薦 | 記憶體硬化、抗 GPU/ASIC、Password Hashing API 原生支援 |
// 1. 雜湊 (預設 Bcrypt, Cost=10)
$hash = password_hash($password, PASSWORD_DEFAULT);
// 或指定 Argon2id (PHP 7.2+)
$hash = password_hash($password, PASSWORD_ARGON2ID, ['memory_cost' => 1<<17, 'time_cost' => 4, 'threads' => 2]);
// 2. 驗證
if (password_verify($inputPassword, $hash)) {
// 登入成功
// 3. 檢查是否需重新雜湊 (如 Cost 變更、演算法升級)
if (password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => 12])) {
$newHash = password_hash($inputPassword, PASSWORD_DEFAULT, ['cost' => 12]);
// 更新資料庫
}
}
// 4. 取得雜湊資訊
$info = password_get_info($hash);
/*
[
'algo' => 1, // PASSWORD_BCRYPT
'algoName' => 'bcrypt',
'options' => ['cost' => 10]
]
*/
password VARCHAR(255):足夠容納 Bcrypt (60 字元)、Argon2id (98 字元) 及未來演算法。// 登入時無縫遷移
if (password_verify($input, $storedHash)) {
if (password_needs_rehash($storedHash, PASSWORD_DEFAULT)) {
$newHash = password_hash($input, PASSWORD_DEFAULT);
// UPDATE users SET password = ? WHERE id = ?
}
// 登入成功
} else {
// 兼容舊 MD5:驗證舊雜湊並升級
if (md5($input) === $storedHash) {
$newHash = password_hash($input, PASSWORD_DEFAULT);
// UPDATE ...
// 登入成功
} else {
// 失敗
}
}
password_hash(password_hash($pwd)) 雙重雜湊 → 降低安全性、浪費效能。password_hash → 破壞內建機制,除非極特殊需求。needs_rehash 平滑遷移。