编程

PHP 8.4 预览: 密码哈希:默认 Bcrypt 成本从 10 改为 12

676 2023-12-13 16:43:00

PHP 8.4 是 PHP 的下一个主版本,离正式发布还有一些时日,但其中有些特性已经通过 RFC 投票并实现了。对那些已实现的新特性、更改及弃用等,本站将提前进行分享,供大家预览。

PHP 8.4 修改了 PHP 内置密码哈希 API 的 cost 参数。

PHP 提供了 password_hashpassword_verifypassword_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 值为 10cost 参数是要使用的迭代次数,作为 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 提供了目前可用的各种硬件的基准测试结果,将计算密码哈希所需的时间与成本 101112 进行了比较。

这种成本的正确平衡是,它应该足够快,可以由服务器进行计算,不会阻碍用户体验;但足够慢,在攻击者获得密码哈希的情况下,很难以暴力方式进行计算。

下表和图表来自 RFC 中提供的数据,以及 PHP 完成的其他基准测试。看对于每个处理器,在各种成本参数中计算哈希的平均时间。所有时间都以毫秒为单位。

 891011121314
Intel i5-2430M
2.4GHz - 2011
2141801613276571319
Apple M1 Pro
2021
153161121240480965
Intel Xeon E-2246G
3.60 GHz - 2019
11203978158316631
Intel Xeon E32145
3.30 GHz - 2011
1632641282565231047
AMD EPYC 7002
2019
142958116244485997
AMD Ryzen 4800H
2.9 GHz - 2020
214560105210416850
Intel Xeon Skylake
IBRS - 2020
132754108216432873
PHP BCrypt Compute Duration Benchmark

向后兼容性影响

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]);