Skip to content
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
wants to merge 114 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 102 commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
fe1c16e
Begin switching the password hashing mechanism from phpass to bcrypt.
johnbillion Sep 11, 2024
82d937f
Update the handling of user passwords, password reset keys, and user …
johnbillion Sep 11, 2024
f12e93d
Update the handling of the recovery mode key.
johnbillion Sep 11, 2024
5a46809
Update the handling of post passwords.
johnbillion Sep 11, 2024
d4e4e47
Automatically rehash user passwords and application passwords after t…
johnbillion Sep 11, 2024
638421d
Docs.
johnbillion Sep 11, 2024
316cb41
Juggle this a bit.
johnbillion Sep 11, 2024
76fa518
Trying to get these tests in order.
johnbillion Sep 11, 2024
d707939
More docs.
johnbillion Sep 11, 2024
4a796b7
Retain validity of phpass hashed password reset keys.
johnbillion Sep 11, 2024
64dfd57
Retain validity of a phpass hashed recovery mode key.
johnbillion Sep 11, 2024
e161994
Retain validity of phpass hashed user request keys.
johnbillion Sep 11, 2024
548f937
More docs.
johnbillion Sep 11, 2024
fe9b053
Add tests for password rehashing when signing in with a username or e…
johnbillion Sep 12, 2024
33cf000
Docs.
johnbillion Sep 12, 2024
d51cbc2
Ensure `wp_check_password()` remains compatible with changes to the d…
johnbillion Sep 12, 2024
6b5441c
Simplify this logic.
johnbillion Sep 12, 2024
89e0645
Add tests for Argon2 support.
johnbillion Sep 12, 2024
085b73c
Always pass the return value of `wp_check_password()` through the `ch…
johnbillion Sep 12, 2024
3e078a7
Reintroduce support for the `$wp_hasher` global if it's set.
johnbillion Sep 12, 2024
be636be
Test the tests.
johnbillion Sep 12, 2024
4265787
Docs.
johnbillion Sep 12, 2024
fd6bcdd
Start splitting up and fixing these tests.
johnbillion Sep 12, 2024
392e612
Coding standards.
johnbillion Sep 12, 2024
9040193
Add a todo.
johnbillion Sep 13, 2024
bc99ca1
Add some more assertions to the application password auth tests.
johnbillion Sep 17, 2024
58a89e2
Correct and add tests for the application password rehashing.
johnbillion Sep 17, 2024
49871d0
Done.
johnbillion Sep 17, 2024
b42e57b
Coding standards.
johnbillion Sep 17, 2024
2baeb19
Allow either the name or password to change in order to allow an appl…
johnbillion Sep 17, 2024
95f6d94
Docs.
johnbillion Sep 17, 2024
f8974eb
PHP 7.2 compatibility.
johnbillion Oct 8, 2024
ff85df2
Docs.
johnbillion Oct 8, 2024
ebcdd26
Add a test to ensure the user's account password isn't touched when r…
johnbillion Oct 8, 2024
140b9d4
Merge branch 'trunk' into 21022-bcrypt
johnbillion Oct 8, 2024
ea8c56c
One fewer regexes in the world.
johnbillion Oct 8, 2024
2ef2077
Merge branch 'trunk' into 21022-bcrypt
johnbillion Oct 9, 2024
3a951ef
Allow the password hashing options to be filtered.
johnbillion Oct 11, 2024
53acf70
Docs.
johnbillion Oct 11, 2024
5944d9a
Merge branch 'trunk' into 21022-bcrypt
johnbillion Oct 21, 2024
820f48f
Cease hashing passwords as md5 during the upgrade routine and/or the …
johnbillion Oct 21, 2024
c07e618
More tests for empty values.
johnbillion Oct 21, 2024
5d33621
More updates to the tests.
johnbillion Oct 21, 2024
0063154
Add a test to verify that a password gets rehashed when the default c…
johnbillion Oct 22, 2024
1aea250
This might as well go here.
johnbillion Oct 22, 2024
d6c7837
Add tests for post password handling.
johnbillion Oct 22, 2024
8b1dbd2
Merge branch 'trunk' into 21022-bcrypt
johnbillion Oct 22, 2024
5824f4c
Clear the cookie value before performing the assertions.
johnbillion Oct 22, 2024
2502179
Implement pre-hashing with sha384 to retain entropy of passwords over…
johnbillion Nov 5, 2024
2321412
Implement domain separation for the password to protect against passw…
johnbillion Nov 11, 2024
d86ef6d
Remove assertions that are unnecessarily specific to bcrypt.
johnbillion Nov 25, 2024
09ddc5e
The default bcrypt cost was increased in PHP 8.4.
johnbillion Nov 25, 2024
a27411d
Merge branch 'trunk' into 21022-bcrypt-sha2
johnbillion Dec 12, 2024
e5f8d5c
Merge branch 'trunk' into 21022-bcrypt
johnbillion Dec 12, 2024
9bb86a8
Merge branch '21022-bcrypt' into 21022-bcrypt-sha2
johnbillion Dec 12, 2024
1cebd80
Switch to HMAC in place of manually prepending the domain separation …
johnbillion Dec 12, 2024
537ee4a
Introduce the `wp_hash_password_algorithm` filter for controlling the…
johnbillion Dec 18, 2024
8a99edd
Why.
johnbillion Dec 18, 2024
68634f0
Vanilla bcrypt hashes should be rehashed to use pre-hashing.
johnbillion Dec 18, 2024
adf983f
Let's bring this more inline with the other tests.
johnbillion Dec 18, 2024
754519b
No need to perform a prefix check here, just let `wp_check_password()…
johnbillion Dec 18, 2024
35d1885
Merge branch 'trunk' into 21022-bcrypt-algo-filter
johnbillion Jan 6, 2025
0c49b94
Docs.
johnbillion Jan 6, 2025
f85a6cb
Merge branch 'trunk' into 21022-bcrypt
johnbillion Jan 6, 2025
413e81f
Switch to using a fast sha1 hash for password reset keys, user reques…
johnbillion Jan 6, 2025
4880c6e
Switch from SHA-1 to SHA-256 in the security key HMACs.
johnbillion Jan 7, 2025
cb46077
Use a more fitting hash prefix.
johnbillion Jan 7, 2025
a9a1c17
Update some more tests.
johnbillion Jan 7, 2025
8b4d40e
Introduce wrapper functions for hashing and checking a value with BLA…
johnbillion Jan 7, 2025
9994922
Replace SHA-256 and SHA-1 with BLAKE2b when hashing of password reset…
johnbillion Jan 7, 2025
d80491d
Switch application passwords over to BLAKE2b instead of phpass.
johnbillion Jan 7, 2025
f222f64
Remove opportunistic rehashing of application passwords.
johnbillion Jan 8, 2025
c966123
The hash extension is now required due to the use of sha384.
johnbillion Jan 8, 2025
7b3eb9a
Enforce an upper key length.
johnbillion Jan 8, 2025
e3fcdca
Ensure the salt satisfies the constraints for key length in Sodium.
johnbillion Jan 9, 2025
1d1ab91
Add some type safety.
johnbillion Jan 9, 2025
a94d7ff
Merge branch 'trunk' into 21022-bcrypt
johnbillion Jan 13, 2025
8e4652e
Merge branch 'trunk' into 21022-application-passwords
johnbillion Jan 13, 2025
20eb361
Remove salting from the fast hashing function.
johnbillion Jan 14, 2025
37e0cd7
Docs.
johnbillion Jan 14, 2025
9a58b91
Tidying up.
johnbillion Jan 14, 2025
d62c5d3
Rename these functions.
johnbillion Jan 14, 2025
5f1a8ee
Docs.
johnbillion Jan 14, 2025
8931037
More docs.
johnbillion Jan 14, 2025
141b031
Merge branch '21022-application-passwords' into 21022-bcrypt
johnbillion Jan 14, 2025
a5c105b
Merge branch 'trunk' into 21022-bcrypt
johnbillion Jan 14, 2025
74f8ecf
More docs!
johnbillion Jan 14, 2025
1d97c7d
This is no longer needed.
johnbillion Jan 16, 2025
5e7e059
Merge branch 'trunk' into 21022-bcrypt
johnbillion Jan 17, 2025
3635023
No need to duplicate this everywhere.
johnbillion Jan 17, 2025
2686d46
Add tests for `wp_fast_hash()` and `wp_verify_fast_hash()` and correc…
johnbillion Jan 17, 2025
1227f17
Ensure that the password fragment used in the auth cookie key always …
johnbillion Jan 17, 2025
66f1bd8
More accurate naming and test coverage declaration.
johnbillion Jan 17, 2025
946c93f
Retain current cookie hashing behaviour for vanilla bcrypt hashes too.
johnbillion Jan 18, 2025
afff6fe
Docs.
johnbillion Jan 18, 2025
0cae584
Add tests for retaining validity of an auth cookie generated with a p…
johnbillion Jan 18, 2025
0bdec8e
Fully reinstall WordPress between performance runs. This ensures ther…
johnbillion Jan 18, 2025
9ee9ee2
Revert "Fully reinstall WordPress between performance runs. This ensu…
johnbillion Jan 19, 2025
b0f83d4
Reorder the performance tests so the base tests run first, followed b…
johnbillion Jan 19, 2025
1c85aea
o_O
johnbillion Jan 19, 2025
540d77b
Reinstate the initial build.
johnbillion Jan 19, 2025
bb1d062
It's Sunday.
johnbillion Jan 19, 2025
0d06290
Merge branch 'trunk' into 21022-bcrypt
johnbillion Feb 1, 2025
c7079c1
Fix some path handling when installing the local environment from the…
johnbillion Feb 1, 2025
59535cd
Merge branch 'trunk' into 21022-bcrypt
johnbillion Feb 3, 2025
4d34c39
Merge branch 'trunk' into 21022-bcrypt
johnbillion Feb 3, 2025
976c255
Mark some parameters as sensitive.
johnbillion Feb 3, 2025
3e8a96e
Merge branch 'trunk' into 21022-bcrypt
johnbillion Feb 12, 2025
935a322
Switch back to phpass for post passwords. We'll revisit this in a fol…
johnbillion Feb 12, 2025
54b722d
Add a user ID argument and a filter to `password_needs_rehash()`.
johnbillion Feb 12, 2025
21e352c
More docs.
johnbillion Feb 12, 2025
7325ca9
More docs.
johnbillion Feb 12, 2025
8bee410
Revert another test change.
johnbillion Feb 12, 2025
c95fe93
This is no longer implemented, but the other tests can remain.
johnbillion Feb 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
# Runs the performance test suite.
performance:
name: ${{ matrix.multisite && 'Multisite' || 'Single site' }}
uses: WordPress/wordpress-develop/.github/workflows/reusable-performance.yml@trunk
uses: ./.github/workflows/reusable-performance.yml
permissions:
contents: read
if: ${{ ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }}
Expand Down
140 changes: 85 additions & 55 deletions .github/workflows/reusable-performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ env:
LOCAL_SAVEQUERIES: false
LOCAL_WP_DEVELOPMENT_MODE: "''"

# This workflow takes two sets of measurements — one for the current commit,
# This workflow takes three measurements — one against the current commit, one against the previous commit,
# and another against a consistent version that is used as a baseline measurement.
# This is done to isolate variance in measurements caused by the GitHub runners
# from differences caused by code changes between commits. The BASE_TAG value here
Expand All @@ -62,48 +62,55 @@ env:

jobs:
# Performs the following steps:
# - Configure environment variables.
# - Checkout repository.
# - Determine the target SHA value (on `workflow_dispatch` only).
# - Set up Node.js.
# - Log debug information.
# - Install npm dependencies.
# - Install Playwright browsers.
# - Build WordPress.
# - Start Docker environment.
# - Install object cache drop-in.
# - Log running Docker containers.
# - Docker debug information.
# - Install WordPress.
# - Enable themes on Multisite.
# - Install WordPress Importer plugin.
# - Import mock data.
# - Deactivate WordPress Importer plugin.
# - Update permalink structure.
# - Install additional languages.
# - Disable external HTTP requests.
# - Disable cron.
# - List defined constants.
# - Install MU plugin.
# - Run performance tests (current commit).
# - Download previous build artifact (target branch or previous commit).
# - Download artifact.
# - Unzip the build.
# - Run any database upgrades.
# - Flush cache.
# - Delete expired transients.
# - Run performance tests (previous/target commit).
# - Set the environment to the baseline version.
# - Run any database upgrades.
# - Flush cache.
# - Delete expired transients.
# - Run baseline performance tests.
# - Archive artifacts.
# - Compare results.
# - Add workflow summary.
# - Set the base sha.
# - Set commit details.
# - Publish performance results.
# - Setup:
# - Configure environment variables.
# - Checkout repository.
# - Determine the target SHA value (on `workflow_dispatch` only).
# - Set up Node.js.
# - Log debug information.
# - Install npm dependencies.
# - Install Playwright browsers.
# - Build WordPress.
# - Start Docker environment.
# - Install object cache drop-in.
# - Log running Docker containers.
# - Docker debug information.
# - Install WordPress.
# - Enable themes on Multisite.
# - Install WordPress Importer plugin.
# - Import mock data.
# - Deactivate WordPress Importer plugin.
# - Update permalink structure.
# - Install additional languages.
# - Disable external HTTP requests.
# - Disable cron.
# - List defined constants.
# - Install MU plugin.
# - Run the baseline tests:
# - Set the environment to the baseline version
# - Run baseline performance tests.
# - Run the previous build tests:
# - Download previous build artifact (target branch or previous commit).
# - Download artifact.
# - Unzip the build.
# - Run any database upgrades.
# - Flush cache.
# - Delete expired transients.
# - Run performance tests (previous/target commit).
# - Run the current tests:
# - Set the environment to the current version.
# - Run the build.
# - Run any database upgrades.
# - Flush cache.
# - Delete expired transients.
# - Run current performance tests.
# - Store and publish results:
# - Archive artifacts.
# - Compare results.
# - Add workflow summary.
# - Set the base sha.
# - Set commit details.
# - Publish performance results.
# - Ensure version-controlled files are not modified or deleted.
performance:
name: ${{ inputs.multisite && 'Multisite' || 'Single site' }} / ${{ inputs.memcached && 'Memcached' || 'Default' }}
Expand Down Expand Up @@ -219,9 +226,30 @@ jobs:
mkdir ./${{ env.LOCAL_DIR }}/wp-content/mu-plugins
cp ./tests/performance/wp-content/mu-plugins/server-timing.php ./${{ env.LOCAL_DIR }}/wp-content/mu-plugins/server-timing.php

- name: Run performance tests (current commit)

# ============================
# Run the baseline tests first
# ============================

- name: Set the environment to the baseline version
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }}
run: |
VERSION="${{ env.BASE_TAG }}"
VERSION="${VERSION%.0}"
npm run env:cli -- core update --version=$VERSION --force --path=/var/www/${{ env.LOCAL_DIR }}
npm run env:cli -- core version --path=/var/www/${{ env.LOCAL_DIR }}

- name: Run baseline performance tests
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }}
env:
TEST_RESULTS_PREFIX: base
run: npm run test:performance


# ============================
# Run the previous build tests
# ============================

- name: Download previous build artifact (target branch or previous commit)
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
id: get-previous-build
Expand Down Expand Up @@ -276,13 +304,13 @@ jobs:
TEST_RESULTS_PREFIX: before
run: npm run test:performance

- name: Set the environment to the baseline version
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }}
run: |
VERSION="${{ env.BASE_TAG }}"
VERSION="${VERSION%.0}"
npm run env:cli -- core update --version=$VERSION --force --path=/var/www/${{ env.LOCAL_DIR }}
npm run env:cli -- core version --path=/var/www/${{ env.LOCAL_DIR }}

# =====================
# Run the current tests
# =====================

- name: Build WordPress
run: npm run build

- name: Run any database upgrades
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }}
Expand All @@ -296,12 +324,14 @@ jobs:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }}
run: npm run env:cli -- transient delete --expired --path=/var/www/${{ env.LOCAL_DIR }}

- name: Run baseline performance tests
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' }}
env:
TEST_RESULTS_PREFIX: base
- name: Run performance tests (current commit)
run: npm run test:performance


# =========================
# Store and publish results
# =========================

- name: Archive artifacts
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
if: always()
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"issues": "https://core.trac.wordpress.org/"
},
"require": {
"ext-hash": "*",
"ext-json": "*",
"php": ">=7.2.24"
},
Expand Down
2 changes: 1 addition & 1 deletion src/wp-admin/includes/class-wp-site-health.php
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ public function get_test_php_extensions() {
),
'hash' => array(
'function' => 'hash',
'required' => false,
'required' => true,
),
'imagick' => array(
'extension' => 'imagick',
Expand Down
8 changes: 1 addition & 7 deletions src/wp-admin/includes/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,7 @@ function upgrade_101() {
*
* @ignore
* @since 1.2.0
* Since x.y.z User passwords are no longer hashed with md5.
*
* @global wpdb $wpdb WordPress database abstraction object.
*/
Expand All @@ -980,13 +981,6 @@ function upgrade_110() {
}
}

$users = $wpdb->get_results( "SELECT ID, user_pass from $wpdb->users" );
foreach ( $users as $row ) {
if ( ! preg_match( '/^[A-Fa-f0-9]{32}$/', $row->user_pass ) ) {
$wpdb->update( $wpdb->users, array( 'user_pass' => md5( $row->user_pass ) ), array( 'ID' => $row->ID ) );
}
}

// Get the GMT offset, we'll use that later on.
$all_options = get_alloptions_110();

Expand Down
48 changes: 47 additions & 1 deletion src/wp-includes/class-wp-application-passwords.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public static function is_in_use() {
*
* @since 5.6.0
* @since 5.7.0 Returns WP_Error if application name already exists.
* @since x.y.z The hashed password value now uses wp_fast_hash() instead of phpass.
*
* @param int $user_id User ID.
* @param array $args {
Expand Down Expand Up @@ -95,7 +96,7 @@ public static function create_new_application_password( $user_id, $args = array(
}

$new_password = wp_generate_password( static::PW_LENGTH, false );
$hashed_password = wp_hash_password( $new_password );
$hashed_password = self::hash_password( $new_password );

$new_item = array(
'uuid' => wp_generate_uuid4(),
Expand Down Expand Up @@ -124,6 +125,7 @@ public static function create_new_application_password( $user_id, $args = array(
* Fires when an application password is created.
*
* @since 5.6.0
* @since x.y.z @since x.y.z The hashed password value now uses wp_fast_hash() instead of phpass.
*
* @param int $user_id The user ID.
* @param array $new_item {
Expand Down Expand Up @@ -249,6 +251,7 @@ public static function application_name_exists_for_user( $user_id, $name ) {
* Updates an application password.
*
* @since 5.6.0
* @since x.y.z The actual password should now be hashed using wp_fast_hash().
*
* @param int $user_id User ID.
* @param string $uuid The password's UUID.
Expand Down Expand Up @@ -296,6 +299,8 @@ public static function update_application_password( $user_id, $uuid, $update = a
* Fires when an application password is updated.
*
* @since 5.6.0
* @since x.y.z The password is now hashed using wp_fast_hash() instead of phpass.
* Existing passwords may still be hashed using phpass.
*
* @param int $user_id The user ID.
* @param array $item {
Expand Down Expand Up @@ -464,4 +469,45 @@ public static function chunk_password( $raw_password ) {

return trim( chunk_split( $raw_password, 4, ' ' ) );
}

/**
* Hashes a plaintext application password.
*
* @since x.y.z
*
* @param string $password Plaintext password.
* @return string Hashed password.
*/
public static function hash_password( string $password ): string {
return wp_fast_hash( $password );
}

/**
* Checks a plaintext application password against a hashed password.
*
* @since x.y.z
*
* @param string $password Plaintext password.
* @param string $hash Hash of the password to check against.
* @return bool Whether the password matches the hashed password.
*/
public static function check_password( string $password, string $hash ): bool {
return wp_verify_fast_hash( $password, $hash );
}

/**
* Checks whether a password hash needs to be rehashed.
*
* A password hashed in a prior version of WordPress may still be hashed with phpass and will
* need to be rehashed. If the algorithm is changed in WordPress then a password hashed in a
* previous version will need to be rehashed.
*
* @since x.y.z
*
* @param string $hash Hash of a password to check.
* @return bool Whether the hash needs to be rehashed.
*/
public static function password_needs_rehash( string $hash ): bool {
return ! str_starts_with( $hash, '$generic$' );
}
}
Loading
Loading