在构建用户认证系统时,安全地处理密码是开发者必须重视的问题。错误地比较密码,可能导致系统受到时序攻击(Timing Attacks)等安全威胁。PHP 提供了一些内建函数,可以帮助我们安全地处理密码验证。其中,crypt() 和 hash_equals() 的组合使用,是一种相对安全的实践方式。
crypt() 是 PHP 的一个加密函数,用于基于某种算法(如 bcrypt、SHA-512 等)对密码进行哈希加密。它的语法如下:
string crypt(string $string, string $salt)
salt 参数决定了加密算法及其行为。对于现代应用,推荐使用 bcrypt,可以通过以下方式自动生成适当的 salt:
$salt = '$2y$10$' . substr(strtr(base64_encode(random_bytes(16)), '+', '.'), 0, 22);
$hash = crypt('mypassword', $salt);
很多初学者会写出如下代码来验证密码:
if (crypt($inputPassword, $storedHash) == $storedHash) {
// 登录成功
}
这在功能上看似可行,但存在严重的安全隐患——时序攻击(Timing Attack)。攻击者可以通过测量程序响应时间的微小差异,逐步猜出正确的密码哈希。
为了解决时序攻击的问题,PHP 从 5.6 开始提供了 hash_equals() 函数,用于安全地比较两个哈希值:
bool hash_equals(string $known_string, string $user_string)
它使用恒定时间算法,不会根据字符串内容提前返回,避免了时间差泄露。
<?php
// 假设我们从数据库获取了加密后的密码哈希
$storedHash = '$2y$10$9YzyYtVht3tcGEn.7PiF2OlRM0HDTrM7Z5D.yPi8hdm0fJeFVKH4K'; // 由 crypt() 生成的 bcrypt 哈希
$inputPassword = $_POST['password'] ?? '';
// 用 crypt() 对用户输入的密码进行哈希,使用存储哈希作为 salt
$inputHash = crypt($inputPassword, $storedHash);
// 使用 hash_equals() 安全比较
if (hash_equals($storedHash, $inputHash)) {
echo "登录成功!";
} else {
echo "密码错误!";
}
?>
虽然 crypt() 仍然可以安全使用,但 PHP 官方更推荐使用 password_hash() 和 password_verify(),因为它们封装了算法选择与安全比较过程,使用更加简单安全。例如:
// 创建密码哈希
$hash = password_hash('mypassword', PASSWORD_BCRYPT);
// 验证密码
if (password_verify($inputPassword, $hash)) {
echo "验证通过";
}
但在某些场景下(如维护旧系统或有特殊需求),crypt() 与 hash_equals() 的组合仍然是一个可接受且安全的方案。
避免使用 == 或 === 比较密码哈希;
使用 crypt() 哈希密码时要正确选择 salt 和算法(推荐 bcrypt);
使用 hash_equals() 进行安全比较,防止时序攻击;
如果没有特定理由,推荐使用 password_hash() 和 password_verify() 替代低层封装。
在处理用户密码时,安全不是可选项,而是必需项。正确的做法不仅能保护用户隐私,还能提升整个系统的抗攻击能力。