在现代 Web 开发中,用户密码的安全存储是一项关键任务。彩虹表攻击是一种常见的密码破解技术,它依赖预计算的哈希值表来快速匹配原始密码。为了有效防御这种攻击,我们必须为密码哈希增加“盐”(salt)。PHP 的 crypt() 函数正是一个可以很好应对这一挑战的工具,但前提是正确使用它。
PHP 的 crypt() 函数用于对字符串进行单向加密,常用于密码哈希。它支持多种加密算法,包括传统的 DES、MD5,以及更安全的 Blowfish($2y$)和 SHA-256/512($5$/$6$)等。
函数原型如下:
string crypt(string $string, string $salt);
其中 $string 是要加密的原始字符串,$salt 决定了使用哪种算法以及加密的变体。
彩虹表攻击依赖于对哈希值的预计算。然而,如果每次哈希的盐值都不同,那么攻击者就无法使用通用的彩虹表进行匹配。crypt() 允许我们自定义盐值,结合强加密算法就能有效避免这种攻击。
举例来说,我们可以这样使用 Blowfish 算法和动态盐值:
$password = 'user-password';
$salt = '$2y$10$' . substr(strtr(base64_encode(random_bytes(16)), '+', '.'), 0, 22);
$hash = crypt($password, $salt);
在上面的代码中:
$2y$10$ 表示使用 Blowfish 算法,成本因子为 10;
random_bytes(16) 生成高强度随机盐;
substr(..., 0, 22) 截取成符合 Blowfish 盐格式的长度。
这样生成的 $hash 就是加盐后的密码哈希。
在用户登录时,我们不需要手动再次生成盐,而是直接用存储在数据库中的哈希作为 salt 参数传入 crypt():
$inputPassword = 'user-input';
$storedHash = '$2y$10$qzWRzYy5q3DvYbEC0KMX4O0PGWlOQxkz7v0QF5OwMqpyG0TzjSAGK'; // 来自数据库
if (hash_equals($storedHash, crypt($inputPassword, $storedHash))) {
echo '密码正确';
} else {
echo '密码错误';
}
这样 crypt() 会自动使用哈希中的盐和算法重新加密输入密码,再进行比较。这种方式简洁且安全。
$hash = crypt($password, 'fixedsalt');
这种方式极易受到彩虹表攻击,因为相同的密码始终对应相同的哈希。
$hash = crypt($password, 'ab'); // DES 算法,盐太短
DES 不仅盐短,而且计算成本低,现代硬件可以轻松暴力破解。
虽然 crypt() 是安全的,但 PHP 从 5.5 起推荐使用 password_hash() 和 password_verify() 这套更现代的密码哈希 API。不过,如果你正在维护老项目或出于兼容性考虑依然使用 crypt(),确保你做到以下几点:
使用强算法(如 $2y$、$5$、$6$);
始终使用随机且足够长的盐值;
永远不要重复使用盐;
盐值应嵌入哈希中存储,而不是单独存储。
// 注册时
$password = $_POST['password'];
$salt = '$2y$12$' . substr(strtr(base64_encode(random_bytes(16)), '+', '.'), 0, 22);
$hash = crypt($password, $salt);
// 存储 $hash 到数据库中
// 登录时
$inputPassword = $_POST['password'];
$storedHash = getHashFromDatabase(); // 从数据库中获取
if (hash_equals($storedHash, crypt($inputPassword, $storedHash))) {
echo '登录成功';
} else {
echo '密码错误';
}
你可以访问 https://m66.net/php-crypt-guide 来获取本教程的在线版本与更多示例。
正确使用 crypt() 函数可以有效防止彩虹表攻击。关键在于使用强加密算法、随机的盐值,以及对比时的安全做法。虽然新项目推荐使用 password_hash() 系列函数,但掌握 crypt() 的安全用法同样是每位 PHP 开发者的必修课。