頻發的「脫庫」事件證明:明文儲存、單一 MD5、無鹽雜湊都是嚴重安全缺陷。用戶習慣跨站複用密碼,一家洩露、全線崩盤。本文梳理密碼儲存演進史,確立現代 PHP 專案的黃金標準。
| 階段 | 做法 | 風險 |
|---|---|---|
| 1. 明文 | 直接存密碼 | 內部人員/駭客直接可見 |
| 2. 單向雜湊 | md5($pwd) | 彩虹表反查、碰撞攻擊 |
| 3. 加鹽雜湊 | hash('sha256', $pwd . $salt) | 固定演算法可被 GPU 大規模並行破解 |
| 4. 慢速雜湊 | Bcrypt / PBKDF2 / Argon2 | ✅ 計算緩慢、記憶體硬化、抗 GPU/ASIC |
Blowfish 加密 + crypt(),自帶鹽值、Cost 因子(迴圈次數)。
<?php
function bcryptHash(string $pwd): string {
if (!defined('CRYPT_BLOWFISH') || !CRYPT_BLOWFISH) {
throw new RuntimeException('Bcrypt 不支援');
}
// $2y$ = Blowfish, 11 = cost (2^11 輪), 22 字元鹽值
$salt = '$2y$11$' . substr(md5(uniqid(rand(), true)), 0, 22);
return crypt($pwd, $salt);
}
function bcryptVerify(string $pwd, string $hash): bool {
return crypt($pwd, $hash) === $hash;
}
?>
注意:鹽值必須以 $2y$ 或 $2a$ 開頭,Cost 建議 10-12(視伺服器 CPU 調整)。
封裝 Bcrypt(預設)或 Argon2(PHP 7.2+),自動處理鹽值、Cost、演算法標識,開發者零負擔。
<?php
// 1. 雜湊(自動生成鹽值、Cost=10、演算法標識)
$hash = password_hash($password, PASSWORD_DEFAULT); // 目前 = Bcrypt
// 指定 Cost
$hash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);
// 指定 Argon2id(PHP 7.3+)
$hash = password_hash($password, PASSWORD_ARGON2ID, ['memory_cost' => 65536, 'time_cost' => 4, 'threads' => 3]);
// 2. 驗證(自動解析 hash 中的演算法/鹽值/Cost)
if (password_verify($password, $hash)) { /* 通過 */ }
// 3. 需重新雜湊?(演算法升級、Cost 變更)
if (password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => 12])) {
$hash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);
// 更新資料庫
}
// 4. 取得雜湊資訊
$info = password_get_info($hash); // ['algo' => 1, 'algoName' => 'bcrypt', 'options' => ['cost' => 10]]
?>
password VARCHAR(255):PASSWORD_DEFAULT 目前輸出 60 字元,未來 Argon2 可能更長,預留 255 最保險。PASSWORD_BCRYPT,CHAR(60) 足夠。password_hash VARCHAR(255) 欄位,legacy_hash 保留舊值。password_hash() 重新雜湊存入新欄位、清空舊欄位。md5(md5($pwd).$salt) 雙層 MD5 仍屬快速雜湊,GPU 秒破。password_hash() + password_verify(),定期檢視 password_needs_rehash() 升級演算法。2024 年後,新專案直接用 password_hash($pwd, PASSWORD_ARGON2ID),舊專案遷移至 PASSWORD_DEFAULT。密碼安全不是選擇題,是底線。