PHP 8.4: Password Hashing: Default Bcrypt cost changed from 10 to 12

Version8.4
TypeChange

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

PHP BCrypt Compute Duration Benchmark

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

RFC Discussion Implementation