PHP 8.4 预览: 密码哈希:默认 Bcrypt 成本从 10 改为 12
PHP 8.4 是 PHP 的下一个主版本,离正式发布还有一些时日,但其中有些特性已经通过 RFC 投票并实现了。对那些已实现的新特性、更改及弃用等,本站将提前进行分享,供大家预览。
PHP 8.4 修改了 PHP 内置密码哈希 API 的 cost 参数。
PHP 提供了 password_hash
、password_verify
和 password_needs_rehash
(还有两个附加函数,用于获取支持的算法列表并从哈希中获取信息) 函数以安全散列密码的函数。密码哈希算法及其参数是可配置的,并且算法和参数存储在密码散列本身中,因此可以使用计算散列的相同参数来验证密码。.
当 PHP 5.5(2013) 中引入密码哈希 API 后,它支持 Bcrypt 作为算法之一,使用 PASSWORD_BCRYPT
常量表示。
password_hash('hunter2', PASSWORD_BCRYPT);
// "$2y$10$GUYJOA4aWc6HegA2x.85PeZ9yjP2HCKFjb44C3vz1jlpUNb4AgYz2"
PHP 5.5 也支持 PASSWORD_DEFAULT
,其被设计为随时间变化。默认算法没有变化,未来也没有改变它的计划。
还可以改变散列算法的参数:
password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);
// "$2y$10$dI8qa2bKk/RN2hemdHkOgu8oICMn7ivhzVyJmiJmNFzjJpYsLEAYK"
对于 PASSWORD_BCRYPT
/ PASSWORD_DEFAULT
,默认的 cost
值为 10
。 cost
参数是要使用的迭代次数,作为 2 的幂。例如,cost 为 10
的输入意味着 2**10
次迭代。
在 PHP 8.4 中,PASSWORD_BCRYPT
/PASSWORD_DEFAULT
算法的 cost
参数已从 10
修改为 12
,与决定默认值时相比,在硬件功能更强的情况下,使密码更具弹性和计算难度。
这一变化实质上等同于:
- password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);
+ password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 12]);
计算成本基准测试
提出这一改变的 RFC
提供了目前可用的各种硬件的基准测试结果,将计算密码哈希所需的时间与成本 10
、11
和 12
进行了比较。
这种成本的正确平衡是,它应该足够快,可以由服务器进行计算,不会阻碍用户体验;但足够慢,在攻击者获得密码哈希的情况下,很难以暴力方式进行计算。
下表和图表来自 RFC 中提供的数据,以及 PHP 完成的其他基准测试。看对于每个处理器,在各种成本参数中计算哈希的平均时间。所有时间都以毫秒为单位。
8 | 9 | 10 | 11 | 12 | 13 | 14 | |
---|---|---|---|---|---|---|---|
Intel i5-2430M 2.4GHz - 2011 | 21 | 41 | 80 | 161 | 327 | 657 | 1319 |
Apple M1 Pro 2021 | 15 | 31 | 61 | 121 | 240 | 480 | 965 |
Intel Xeon E-2246G 3.60 GHz - 2019 | 11 | 20 | 39 | 78 | 158 | 316 | 631 |
Intel Xeon E32145 3.30 GHz - 2011 | 16 | 32 | 64 | 128 | 256 | 523 | 1047 |
AMD EPYC 7002 2019 | 14 | 29 | 58 | 116 | 244 | 485 | 997 |
AMD Ryzen 4800H 2.9 GHz - 2020 | 21 | 45 | 60 | 105 | 210 | 416 | 850 |
Intel Xeon Skylake IBRS - 2020 | 13 | 27 | 54 | 108 | 216 | 432 | 873 |
向后兼容性影响
PHP 8.4 将 PASSWORD_BCRYPT
/ PASSWORD_DEFAULT
的默认成本参数更改为 12
。任何明确设置 cost 的 PHP 应用程序都不应该有任何变化。
PASSWORD_ARGON2I
或 PASSWORD_ARGON2ID
的参数保持不变,使用它们的应用程序也不会产生任何影响。
使用默认 cost
参数的应用程序将经历稍长的密码计算时间,但这是预期的结果,会增加攻击者试图强行使用密码哈希的计算时间。
自动重新哈希
当密码哈希 API 被引入 PHP 时,建议在成功调用 password_verify
之后调用 password_needs_reash
函数,以检查是否需要重新使用密码。
以下是使用 PASSWORD_BCRYPT
时的一个示例,用于检查密码是否需要重新散列。由于 PHP 8.4 中的 cost 参数发生了更改,password_needs_rehash
在默认值下返回 true
。如果更新后的哈希存储在数据库中并在之后使用,则这是一次性的重新哈希。
// When user logs in:
if (password_verify($password, $hash)) {
// Password is correct.
// Check if the password needs to be rehashed:
if (password_needs_rehash($hash, PASSWORD_BCRYPT)) {
// If so, create a new hash, and replace the old one
$newHash = password_hash($password, PASSWORD_BCRYPT);
// Update the user record with the $newHash
// ...
}
// Continue with the login process.
}
如何恢复到旧的 cost
参数
在服务器运行非常旧或 CPU 不足的情况下,或者如果应用需要在以前的默认 cost
值 10 上运行,则可以通过显式设置 cost
参数来恢复到旧的哈希计算时间。
- password_hash('hunter2', PASSWORD_BCRYPT);
+ password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);
- password_hash('hunter2', PASSWORD_DEFAULT);
+ password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);