crypt() 是 PHP 早期提供的單向加密函數,基於系統底層 crypt(3) 實現。雖曾廣泛用於密碼存儲,但現已不推薦用於新專案。本文解析其原理、實現認證流程、安全缺陷及遷移路徑。
string crypt(string $string, string $salt = ''): string
$string:待加密明文$salt:干擾串,影響加密結果,防彩虹表攻擊$salt + 哈希值(驗證時需用完整返回值作 salt)| 算法常量 | Salt 長度/前綴 | 備註 |
|---|---|---|
| CRYPT_STD_DES | 2 字符 | 預設,極不安全 |
| CRYPT_EXT_DES | 9 字符 | 擴展 DES |
| CRYPT_MD5 | 12 字符,以 $1$ 開頭 | MD5-based |
| CRYPT_BLOWFISH | 16 字符,以 $2$/$2a$/$2y$ 開頭 | Bcrypt,較安全 |
| CRYPT_SHA256 | 16 字符,$5$ | SHA-256 crypt |
| CRYPT_SHA512 | 16 字符,$6$ | SHA-512 crypt |
substr($password, 0, 2) 使 salt 與密碼綁定,失去隨機性,等同無 saltmysql_* 函數 PHP 7.0+ 已移除== 比較字符串,可被側通道攻擊require 頁面,無 Session、CSRF 保護
PHP 5.5+ 內建,自動處理算法選擇、Salt 生成、成本因子、驗證:
12]);
// $2y$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// 驗證(自動從 hash 解析算法、salt、cost)
if (password_verify('input_pass', $hash)) {
echo '驗證通過';
}
// 檢查是否需重新哈希(算法升級、cost 變更)
if (password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => 14])) {
$newHash = password_hash('StrongPass123!', PASSWORD_DEFAULT, ['cost' => 14]);
// 更新資料庫
}
prepare("SELECT id, password_hash FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if (!$user) return null; // 用戶不存在(用固定時間延遲防枚舉)
// 情況1:已是 password_hash 格式
if (str_starts_with($user['password_hash'], '$2y$') ||
str_starts_with($user['password_hash'], '$argon2')) {
if (password_verify($password, $user['password_hash'])) {
// 檢查需否升級
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
$newHash = password_hash($password, PASSWORD_DEFAULT);
$pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?")
->execute([$newHash, $user['id']]);
}
return $user;
}
}
// 情況2:舊 crypt() 格式(如 $1$ MD5 或 DES)
elseif (verifyLegacyCrypt($password, $user['password_hash'])) {
// 驗證通過,升級為現代哈希
$newHash = password_hash($password, PASSWORD_DEFAULT);
$pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?")
->execute([$newHash, $user['id']]);
return $user;
}
return null; // 密碼錯誤
}
function verifyLegacyCrypt(string $password, string $hash): bool {
// 兼容舊 crypt 驗證
return hash_equals(crypt($password, $hash), $hash);
}
crypt() 是歷史產物,新代碼嚴禁使用password_hash() + password_verify() + PASSWORD_DEFAULTVARCHAR(255) 足夠存儲所有算法哈希