PHP 8.4: Password Hashing: Default Bcrypt cost changed from 10
to 12
PHP 8.4 changes the default cost parameter of PHP's built-in password hashing API.
PHP provides password_hash
, password_verify
, and password_needs_rehash
(with two additional functions to get a list of supported algorithms and to get information from a hash) functions to securely hash passwords. The password hashing algorithm and its parameters are configurable, and the algorithm and parameters are stored in the password hash itself, so the password can be verified using the same parameters from which the hash was calculated.
When the Password Hashing API was introduced in PHP 5.5 (2013), it supported Bcrypt as one of the algorithms, indicated by the PASSWORD_BCRYPT
constant.
password_hash('hunter2', PASSWORD_BCRYPT);
// "$2y$10$GUYJOA4aWc6HegA2x.85PeZ9yjP2HCKFjb44C3vz1jlpUNb4AgYz2"
PHP 5.5 also supported PASSWORD_DEFAULT
, which was designed to change over time. There have been no changes to the default algorithm, and there are no plans to change it in the future yet either.
It is also possible to change the parameters of the hashing algorithm:
password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);
// "$2y$10$dI8qa2bKk/RN2hemdHkOgu8oICMn7ivhzVyJmiJmNFzjJpYsLEAYK"
For PASSWORD_BCRYPT
/ PASSWORD_DEFAULT
, the default cost
value was 10
. The cost
parameter is the number of iterations to use, entered as a power of 2. For instance, a cost
input of 10
means 2**10
iterations.
In PHP 8.4, the cost
parameter of the PASSWORD_BCRYPT
/PASSWORD_DEFAULT
algorithm has changed from 10
to 12
to make the passwords more resilient and challenging to calculate given the more capable hardware available compared to when the default value was decided.
This change is essentially equivalent to:
- password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);
+ password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 12]);
Computational Cost Benchmarks
The RFC that proposed this change provided benchmark results on various hardware available today, comparing the time it takes to compute a password hash with costs 10
, 11
, and 12
.
The right balance of this cost is that it should be fast enough to calculate by the server to not hinder the user experience, but slow enough to make it difficult to compute in a brute-force fashion in case an attacker obtains the password hashes.
The following table and chart are from the data provided in the RFC, as well as additional benchmarks done by PHP.Watch. For each processor, the average time to compute the hash in various cost
parameters. All the times are in milliseconds.
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 |
Backward Compatibility Impact
PHP 8.4 changes the default cost
parameter of PASSWORD_BCRYPT
/ PASSWORD_DEFAULT
to 12
. Any PHP application that explicitly sets the cost should expect no change at all.
Parameters for PASSWORD_ARGON2I
or PASSWORD_ARGON2ID
remain unchanged, and applications that use them will not have any impact either.
Applications using the default cost
parameters will experience a slightly longer password computation time, but this is the expected outcome, to increase the computation time for attackers trying to brute-force the password hashes.
Automatic Re-Hashing
When the Password Hashing API was introduced to PHP, it was recommended to call the password_needs_rehash
function after a successful password_verify
call to check if the password needs to be re-hashed.
The following is an example when using PASSWORD_BCRYPT
, that checks if the password needs to be re-hashed. Because the cost
parameter has changed in PHP 8.4, password_needs_rehash
returns true
under default values. This is a one-time re-hash if the updated hash is stored in the database and is used afterward.
// 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.
}
How to revert to the old cost
parameter
In the case of servers running severely old or under-powered CPUs, or if the application needs to run on the previous default cost
value of 10
, it is possible to revert to the old hash computation times by explicitly setting the cost
parameter.
- password_hash('hunter2', PASSWORD_BCRYPT);
+ password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);
- password_hash('hunter2', PASSWORD_DEFAULT);
+ password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);