mirrored from git://develop.git.wordpress.org/
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#21022 Switch to using bcrypt for hashing passwords #7333
Open
johnbillion
wants to merge
102
commits into
WordPress:trunk
Choose a base branch
from
johnbillion:21022-bcrypt
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,471
−208
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…hey are validated.
This comment was marked as outdated.
This comment was marked as outdated.
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
johnbillion
changed the title
#21022 Switch to using bcrypt for password and security key hashing
#21022 Switch to using bcrypt for hashing passwords and security keys
Sep 11, 2024
…efault bcrypt cost.
…eck_password` filter.
…t the hash prefix handling.
…uses 4 characters from the actual hash, ignoring the prefix. The `$argon2id$` prefix is 10 characters long. Without this change, only 2 characters from its actual hash would be used for the fragment.
…hpass or plain bcrypt hash fragment.
…e are no unwanted artifacts remaining in the database.
…res there are no unwanted artifacts remaining in the database." This reverts commit 0bdec8e.
…y the target, followed by current.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Latest approach to switching from phpass to bcrypt via the native PHP password hashing functions for the following features:
The following features are switched from phpass to the cryptographically secure BLAKE2b hash via Sodium:
Lots more info about the change can be found in the draft for the make/core announcement post.
Tickets
Notes
wp_check_password()
remains compatible with phpass hashes, so password checks will continue to succeed when checking a password against its existing hash. There is no need for users to change or resave their password.wp_authenticate_username_password()
orwp_authenticate_email_password()
.wp_check_password()
and the rehashing that occurs in the above functions are forward-compatible with future changes to the default bcrypt cost (the default bcrypt cost was increased in PHP 8.4), so password checks will continue to succeed when checking a password against its existing hash.wp_hash_password()
andwp_check_password()
retain support for a global$wp_hasher
object which can be used to override the password hashing and checking mechanisms.Elsewhere
FAQs
What about salting?
Salting a bcrypt hash is handled by
password_hash()
andpassword_verify()
. There is no need to implement salting in userland code.What about peppering?
Peppering effectively eliminates the ability to crack a password given only its hash by introducing a secret which needs to be stored outside of the database and is used as part of the value that's hashed, as long as the pepper remains secret. This is compelling, however the portability of a password hash is also eliminated and if the secret pepper is lost, changed, or differs between environments, then all password hashes are invalidated. All users will need to reset their passwords in order to log in, and all application passwords and post passwords will need to be changed.
While a secret pepper does prevent an attacker from being able to crack a password hash if they gain access only to the database, the potential usability tradeoff is high. In addition, the intention of switching to bcrypt is to switch to a password hashing algorithm that is highly resistant to cracking in the first place, thus reducing the benefit gained from peppering.
What about layering hashes to immediately protect legacy hashes?
Hash layering is the process of taking an existing password hash (for example one hashed with phpass) and applying the new hashing algorithm on top of it (bcrypt in this case). The intention is to immediately protect stored passwords with the new hashing algorithm instead of waiting for users to log in or change their passwords in order to rehash the password.
A concern is the length of time it could take to rehash all passwords in the database during the upgrade routine. Handling would need to be implemented to cover usage of passwords (for example logging in or changing passwords) while the password upgrade routine runs.
Additional risks include null byte characters present in the raw output of other hashing algorithms, and password shucking. OWASP warns that layering hashes can make the password easier to crack.
None of this is insurmountable, but it adds complexity for what is in most cases a short term benefit. For this reason, hash layering has not been implemented.
What about the 72 byte limit of bcrypt?
This is addressed by pre-hashing the password with SHA-384 and base64 encoding it prior to hashing with bcrypt. There is discussion about this in the comments here and on #21022.
Supporting references:
What about DoS attacks from long passwords?
The bcrypt implementation in PHP is not susceptible to a DoS attack from long passwords because only the first 72 bytes of the password value are read, therefore there is no need to guard against a long password value prior to it being hashed or checked.
While phpass can be susceptible to a DoS attack via a very long password value, the phpass implementation in WordPress protects against this via a 4096 byte limit when hashing a password and when checking a password. This is unrelated to the password length limit discussed above.
What about the cost factor?
The default cost factor will be used. This is 10 in PHP up to 8.3 and has been increased to 12 in PHP 8.4. Hashes remain portable between installations of PHP that use different cost factors because the cost factor is encoded in the hash output.
It's beyond the scope of WordPress to make adjustments to the cost factor used by bcrypt. If you are planning on updating to PHP 8.4 then you should consider whether the default cost is appropriate for the resources available on your server. The
wp_hash_password_options
filter is available to change the cost factor should it be needed.What about using
PASSWORD_DEFAULT
instead ofPASSWORD_BCRYPT
?The intention of the
PASSWORD_DEFAULT
constant in PHP is to take advantage of future changes to the default algorithm that's used to hash passwords. There are currently no public plans to change this algorithm (at least, I haven't found any), but for safety it makes sense to be explicit about the use of bcrypt in WordPress. This can easily be changed in the future.What about Argon2?
If your server supports the argon2id algorithm, switching to it is now a one-liner:
Unfortunately it's not possible to rely on argon2 being available because it requires both
libargon2
to be available on the server and for PHP to be built with argon2 support enabled. Using argon2 viasodium_compat
still requires the optionallibsodium
extension to be installed. Conditionally using argon2i, argon2id, or bcrypt depending on what is available on the server would increase complexity and limit the portability of hashes.What about scrypt?
There is no native support for scrypt in PHP, and using scrypt via
sodium_compat
still requires the optionallibsodium
extension to be installedIs this change compatible with existing plugins that implement bcrypt hashing?
Yes. If you've used such a plugin to hash passwords with bcrypt then those hashes are compatible with this bcrypt implementation and you should be able to remove the plugin.
What effect will this have on my database when a user logs in?
The first time that each user subsequently logs in after this change is deployed to your site, their password will be rehashed using bcrypt and the value stored in the database. This will result in an additional
UPDATE
query to update theiruser_pass
field in theusers
table.The query will look something like this:
This query performs an
UPDATE
but makes use of the primary key to target the row that needs updating, therefore it should remain very performant. The query only runs once for each user. When they subsequently log in again at a later date their password will not need to be rehashed again.Note that when a user logs in, WordPress already writes to the database via an
UPDATE
query on theusermeta
table to store their updated user session information in thesession_tokens
meta field. This happens every time a user logs in.How do I use an algorithm other than bcrypt on my website?
If your server supports the argon2id algorithm, switching to it is now a one-liner:
If necessary, the
password_algos()
function can be used to check for argon2id support.Alternatively, the
wp_hash_password()
,wp_check_password()
, andwp_password_needs_rehash()
functions are all pluggable. See wp-password-bcrypt as an example of overwriting them in a plugin.Alternatively again, if you need to temporarily or permanently stick with phpass for user passwords and post passwords then you can instantiate the
$wp_hasher
global to restore the previous behaviour. This will not affect application passwords or security keys.Why switch to BLAKE2b for application passwords and security keys?
Switching from phpass to the cryptographically secure but fast BLAKE2b algorithm via Sodium is safe for application passwords and security keys which are randomly generated with sufficiently high entropy. Security keys and application passwords are all randomly generated with high entropy via
wp_generate_password()
from an alpha-numeric character set of size 62. BLAKE2b is highly resistant to preimage attacks (being able to reverse a hash to determine its input) while having a low computational cost.usermeta.meta_value
options.option_value
users.user_activation_key
posts.post_password
These hashes are generated via the new
wp_fast_hash()
function which in turn usessodium_crypto_generichash()
to generate the hash.Existing hashes generated prior to WordPress x.y will remain valid as they are checked via the new
wp_verify_fast_hash()
function which includes fallback support for phpass portable hashes.Is the Sodium extension now required?
No. The Sodium functions that are used are included in the
sodium_compat
library which is loaded when thelibsodium
extension isn't installed. This library has been bundled with WordPress since version 5.2.Todo
hash_hmac( 'sha382' )
means that thehash
extension is required on PHP 7.2 and 7.3 because thehash_hmac()
compat function in compat.php only supports md5 and sha1.password_hash()
should be filterable.wp_hash_password()
andwp_check_password()
need to retain back-compat support for the global$wp_hasher
Testing
There's good test coverage for this change. Here are some manual testing steps that can be taken.
Remaining logged in after the update
user_pass
field for your user account in thewp_users
table in the database has been updated -- it should be prefixed with$wp$2y$
instead of$P$
Post passwords
Password resets
Personal data requests