diff --git a/wp-admin/includes/upgrade.php b/wp-admin/includes/upgrade.php index 94f40cda31..d05bf15a48 100644 --- a/wp-admin/includes/upgrade.php +++ b/wp-admin/includes/upgrade.php @@ -980,6 +980,7 @@ function upgrade_101() { * * @ignore * @since 1.2.0 + * @since 6.8.0 User passwords are no longer hashed with md5. * * @global wpdb $wpdb WordPress database abstraction object. */ @@ -995,13 +996,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(); diff --git a/wp-includes/class-wp-application-passwords.php b/wp-includes/class-wp-application-passwords.php index 4396579059..8ed02dd6f3 100644 --- a/wp-includes/class-wp-application-passwords.php +++ b/wp-includes/class-wp-application-passwords.php @@ -60,6 +60,7 @@ class WP_Application_Passwords { * * @since 5.6.0 * @since 5.7.0 Returns WP_Error if application name already exists. + * @since 6.8.0 The hashed password value now uses wp_fast_hash() instead of phpass. * * @param int $user_id User ID. * @param array $args { @@ -95,7 +96,7 @@ class WP_Application_Passwords { } $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(), @@ -124,6 +125,7 @@ class WP_Application_Passwords { * Fires when an application password is created. * * @since 5.6.0 + * @since 6.8.0 The hashed password value now uses wp_fast_hash() instead of phpass. * * @param int $user_id The user ID. * @param array $new_item { @@ -249,6 +251,7 @@ class WP_Application_Passwords { * Updates an application password. * * @since 5.6.0 + * @since 6.8.0 The actual password should now be hashed using wp_fast_hash(). * * @param int $user_id User ID. * @param string $uuid The password's UUID. @@ -296,6 +299,8 @@ class WP_Application_Passwords { * Fires when an application password is updated. * * @since 5.6.0 + * @since 6.8.0 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 { @@ -467,4 +472,36 @@ class WP_Application_Passwords { return trim( chunk_split( $raw_password, 4, ' ' ) ); } + + /** + * Hashes a plaintext application password. + * + * @since 6.8.0 + * + * @param string $password Plaintext password. + * @return string Hashed password. + */ + public static function hash_password( + #[\SensitiveParameter] + string $password + ): string { + return wp_fast_hash( $password ); + } + + /** + * Checks a plaintext application password against a hashed password. + * + * @since 6.8.0 + * + * @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( + #[\SensitiveParameter] + string $password, + string $hash + ): bool { + return wp_verify_fast_hash( $password, $hash ); + } } diff --git a/wp-includes/class-wp-recovery-mode-key-service.php b/wp-includes/class-wp-recovery-mode-key-service.php index 38d5730f85..efd15e60a3 100644 --- a/wp-includes/class-wp-recovery-mode-key-service.php +++ b/wp-includes/class-wp-recovery-mode-key-service.php @@ -37,29 +37,18 @@ final class WP_Recovery_Mode_Key_Service { * Creates a recovery mode key. * * @since 5.2.0 - * - * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. + * @since 6.8.0 The stored key is now hashed using wp_fast_hash() instead of phpass. * * @param string $token A token generated by {@see generate_recovery_mode_token()}. * @return string Recovery mode key. */ public function generate_and_store_recovery_mode_key( $token ) { - - global $wp_hasher; - $key = wp_generate_password( 22, false ); - if ( empty( $wp_hasher ) ) { - require_once ABSPATH . WPINC . '/class-phpass.php'; - $wp_hasher = new PasswordHash( 8, true ); - } - - $hashed = $wp_hasher->HashPassword( $key ); - $records = $this->get_keys(); $records[ $token ] = array( - 'hashed_key' => $hashed, + 'hashed_key' => wp_fast_hash( $key ), 'created_at' => time(), ); @@ -85,16 +74,12 @@ final class WP_Recovery_Mode_Key_Service { * * @since 5.2.0 * - * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. - * * @param string $token The token used when generating the given key. - * @param string $key The unhashed key. + * @param string $key The plain text key. * @param int $ttl Time in seconds for the key to be valid for. * @return true|WP_Error True on success, error object on failure. */ public function validate_recovery_mode_key( $token, $key, $ttl ) { - global $wp_hasher; - $records = $this->get_keys(); if ( ! isset( $records[ $token ] ) ) { @@ -109,12 +94,7 @@ final class WP_Recovery_Mode_Key_Service { return new WP_Error( 'invalid_recovery_key_format', __( 'Invalid recovery key format.' ) ); } - if ( empty( $wp_hasher ) ) { - require_once ABSPATH . WPINC . '/class-phpass.php'; - $wp_hasher = new PasswordHash( 8, true ); - } - - if ( ! $wp_hasher->CheckPassword( $key, $record['hashed_key'] ) ) { + if ( ! wp_verify_fast_hash( $key, $record['hashed_key'] ) ) { return new WP_Error( 'hash_mismatch', __( 'Invalid recovery key.' ) ); } @@ -169,9 +149,20 @@ final class WP_Recovery_Mode_Key_Service { * Gets the recovery key records. * * @since 5.2.0 + * @since 6.8.0 Each key is now hashed using wp_fast_hash() instead of phpass. + * Existing keys may still be hashed using phpass. * - * @return array Associative array of $token => $data pairs, where $data has keys 'hashed_key' - * and 'created_at'. + * @return array { + * Associative array of token => data pairs, where the data is an associative + * array of information about the key. + * + * @type array ...$0 { + * Information about the key. + * + * @type string $hashed_key The hashed value of the key. + * @type int $created_at The timestamp when the key was created. + * } + * } */ private function get_keys() { return (array) get_option( $this->option_name, array() ); @@ -181,9 +172,19 @@ final class WP_Recovery_Mode_Key_Service { * Updates the recovery key records. * * @since 5.2.0 + * @since 6.8.0 Each key should now be hashed using wp_fast_hash() instead of phpass. * - * @param array $keys Associative array of $token => $data pairs, where $data has keys 'hashed_key' - * and 'created_at'. + * @param array $keys { + * Associative array of token => data pairs, where the data is an associative + * array of information about the key. + * + * @type array ...$0 { + * Information about the key. + * + * @type string $hashed_key The hashed value of the key. + * @type int $created_at The timestamp when the key was created. + * } + * } * @return bool True on success, false on failure. */ private function update_keys( array $keys ) { diff --git a/wp-includes/class-wp-user-request.php b/wp-includes/class-wp-user-request.php index 8c66dcdf81..dc8ca7cdbd 100644 --- a/wp-includes/class-wp-user-request.php +++ b/wp-includes/class-wp-user-request.php @@ -92,6 +92,8 @@ final class WP_User_Request { * Key used to confirm this request. * * @since 4.9.6 + * @since 6.8.0 The key is now hashed using wp_fast_hash() instead of phpass. + * * @var string */ public $confirm_key = ''; diff --git a/wp-includes/class-wp-user.php b/wp-includes/class-wp-user.php index 0be1b3ed02..a5312b664b 100644 --- a/wp-includes/class-wp-user.php +++ b/wp-includes/class-wp-user.php @@ -11,6 +11,7 @@ * Core class used to implement the WP_User object. * * @since 2.0.0 + * @since 6.8.0 The `user_pass` property is now hashed using bcrypt instead of phpass. * * @property string $nickname * @property string $description diff --git a/wp-includes/functions.php b/wp-includes/functions.php index fbad1f721a..af88d3b06d 100644 --- a/wp-includes/functions.php +++ b/wp-includes/functions.php @@ -9114,3 +9114,62 @@ function wp_is_heic_image_mime_type( $mime_type ) { return in_array( $mime_type, $heic_mime_types, true ); } + +/** + * Returns a cryptographically secure hash of a message using a fast generic hash function. + * + * Use the wp_verify_fast_hash() function to verify the hash. + * + * This function does not salt the value prior to being hashed, therefore input to this function must originate from + * a random generator with sufficiently high entropy, preferably greater than 128 bits. This function is used internally + * in WordPress to hash security keys and application passwords which are generated with high entropy. + * + * Important: + * + * - This function must not be used for hashing user-generated passwords. Use wp_hash_password() for that. + * - This function must not be used for hashing other low-entropy input. Use wp_hash() for that. + * + * The BLAKE2b algorithm is used by Sodium to hash the message. + * + * @since 6.8.0 + * + * @throws TypeError Thrown by Sodium if the message is not a string. + * + * @param string $message The message to hash. + * @return string The hash of the message. + */ +function wp_fast_hash( + #[\SensitiveParameter] + string $message +): string { + return '$generic$' . sodium_bin2hex( sodium_crypto_generichash( $message ) ); +} + +/** + * Checks whether a plaintext message matches the hashed value. Used to verify values hashed via wp_fast_hash(). + * + * The function uses Sodium to hash the message and compare it to the hashed value. If the hash is not a generic hash, + * the hash is treated as a phpass portable hash in order to provide backward compatibility for application passwords + * which were hashed using phpass prior to WordPress 6.8.0. + * + * @since 6.8.0 + * + * @throws TypeError Thrown by Sodium if the message is not a string. + * + * @param string $message The plaintext message. + * @param string $hash Hash of the message to check against. + * @return bool Whether the message matches the hashed message. + */ +function wp_verify_fast_hash( + #[\SensitiveParameter] + string $message, + string $hash +): bool { + if ( ! str_starts_with( $hash, '$generic$' ) ) { + // Back-compat for old phpass hashes. + require_once ABSPATH . WPINC . '/class-phpass.php'; + return ( new PasswordHash( 8, true ) )->CheckPassword( $message, $hash ); + } + + return hash_equals( $hash, wp_fast_hash( $message ) ); +} diff --git a/wp-includes/pluggable.php b/wp-includes/pluggable.php index bc61ba0bdf..0ee5891c96 100644 --- a/wp-includes/pluggable.php +++ b/wp-includes/pluggable.php @@ -693,6 +693,7 @@ if ( ! function_exists( 'wp_validate_auth_cookie' ) ) : * * @param string $cookie Optional. If used, will validate contents instead of cookie's. * @param string $scheme Optional. The cookie scheme to use: 'auth', 'secure_auth', or 'logged_in'. + * Note: This does *not* default to 'auth' like other cookie functions. * @return int|false User ID if valid cookie, false if invalid. */ function wp_validate_auth_cookie( $cookie = '', $scheme = '' ) { @@ -768,7 +769,13 @@ if ( ! function_exists( 'wp_validate_auth_cookie' ) ) : return false; } - $pass_frag = substr( $user->user_pass, 8, 4 ); + if ( str_starts_with( $user->user_pass, '$P$' ) || str_starts_with( $user->user_pass, '$2y$' ) ) { + // Retain previous behaviour of phpass or vanilla bcrypt hashed passwords. + $pass_frag = substr( $user->user_pass, 8, 4 ); + } else { + // Otherwise, use a substring from the end of the hash to avoid dealing with potentially long hash prefixes. + $pass_frag = substr( $user->user_pass, -4 ); + } $key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme ); @@ -869,7 +876,13 @@ if ( ! function_exists( 'wp_generate_auth_cookie' ) ) : $token = $manager->create( $expiration ); } - $pass_frag = substr( $user->user_pass, 8, 4 ); + if ( str_starts_with( $user->user_pass, '$P$' ) || str_starts_with( $user->user_pass, '$2y$' ) ) { + // Retain previous behaviour of phpass or vanilla bcrypt hashed passwords. + $pass_frag = substr( $user->user_pass, 8, 4 ); + } else { + // Otherwise, use a substring from the end of the hash to avoid dealing with potentially long hash prefixes. + $pass_frag = substr( $user->user_pass, -4 ); + } $key = wp_hash( $user->user_login . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme ); @@ -2625,8 +2638,9 @@ if ( ! function_exists( 'wp_hash_password' ) ) : * instead use the other package password hashing algorithm. * * @since 2.5.0 + * @since 6.8.0 The password is now hashed using bcrypt by default instead of phpass. * - * @global PasswordHash $wp_hasher PHPass object. + * @global PasswordHash $wp_hasher phpass object. * * @param string $password Plain text user password to hash. * @return string The hash string of the password. @@ -2637,13 +2651,62 @@ if ( ! function_exists( 'wp_hash_password' ) ) : ) { global $wp_hasher; - if ( empty( $wp_hasher ) ) { - require_once ABSPATH . WPINC . '/class-phpass.php'; - // By default, use the portable hash from phpass. - $wp_hasher = new PasswordHash( 8, true ); + if ( ! empty( $wp_hasher ) ) { + return $wp_hasher->HashPassword( trim( $password ) ); } - return $wp_hasher->HashPassword( trim( $password ) ); + if ( strlen( $password ) > 4096 ) { + return '*'; + } + + /** + * Filters the hashing algorithm to use in the password_hash() and password_needs_rehash() functions. + * + * The default is the value of the `PASSWORD_BCRYPT` constant which means bcrypt is used. + * + * **Important:** The only password hashing algorithm that is guaranteed to be available across PHP + * installations is bcrypt. If you use any other algorithm you must make sure that it is available on + * the server. The `password_algos()` function can be used to check which hashing algorithms are available. + * + * The hashing options can be controlled via the {@see 'wp_hash_password_options'} filter. + * + * Other available constants include: + * + * - `PASSWORD_ARGON2I` + * - `PASSWORD_ARGON2ID` + * - `PASSWORD_DEFAULT` + * + * @since 6.8.0 + * + * @param string $algorithm The hashing algorithm. Default is the value of the `PASSWORD_BCRYPT` constant. + */ + $algorithm = apply_filters( 'wp_hash_password_algorithm', PASSWORD_BCRYPT ); + + /** + * Filters the options passed to the password_hash() and password_needs_rehash() functions. + * + * The default hashing algorithm is bcrypt, but this can be changed via the {@see 'wp_hash_password_algorithm'} + * filter. You must ensure that the options are appropriate for the algorithm in use. + * + * @since 6.8.0 + * + * @param array $options Array of options to pass to the password hashing functions. + * By default this is an empty array which means the default + * options will be used. + * @param string $algorithm The hashing algorithm in use. + */ + $options = apply_filters( 'wp_hash_password_options', array(), $algorithm ); + + // Algorithms other than bcrypt don't need to use pre-hashing. + if ( PASSWORD_BCRYPT !== $algorithm ) { + return password_hash( $password, $algorithm, $options ); + } + + // Use SHA-384 to retain entropy from a password that's longer than 72 bytes, and a `wp-sha384` key for domain separation. + $password_to_hash = base64_encode( hash_hmac( 'sha384', trim( $password ), 'wp-sha384', true ) ); + + // Add a prefix to facilitate distinguishing vanilla bcrypt hashes. + return '$wp' . password_hash( $password_to_hash, $algorithm, $options ); } endif; @@ -2651,23 +2714,24 @@ if ( ! function_exists( 'wp_check_password' ) ) : /** * Checks a plaintext password against a hashed password. * - * Maintains compatibility between old version and the new cookie authentication - * protocol using PHPass library. The $hash parameter is the encrypted password - * and the function compares the plain text password when encrypted similarly - * against the already encrypted password to see if they match. + * Note that this function may be used to check a value that is not a user password. + * A plugin may use this function to check a password of a different type, and there + * may not always be a user ID associated with the password. * * For integration with other applications, this function can be overwritten to * instead use the other package password hashing algorithm. * * @since 2.5.0 + * @since 6.8.0 Passwords in WordPress are now hashed with bcrypt by default. A + * password that wasn't hashed with bcrypt will be checked with phpass. + * Passwords hashed with md5 are no longer supported. * - * @global PasswordHash $wp_hasher PHPass object used for checking the password - * against the $hash + $password. - * @uses PasswordHash::CheckPassword + * @global PasswordHash $wp_hasher phpass object. Used as a fallback for verifying + * passwords that were hashed with phpass. * - * @param string $password Plaintext user's password. - * @param string $hash Hash of the user's password to check against. - * @param string|int $user_id Optional. User ID. + * @param string $password Plaintext password. + * @param string $hash Hash of the password to check against. + * @param string|int $user_id Optional. ID of a user associated with the password. * @return bool False, if the $password does not match the hashed password. */ function wp_check_password( @@ -2678,45 +2742,107 @@ if ( ! function_exists( 'wp_check_password' ) ) : ) { global $wp_hasher; - // If the hash is still md5... - if ( strlen( $hash ) <= 32 ) { - $check = hash_equals( $hash, md5( $password ) ); - if ( $check && $user_id ) { - // Rehash using new hash. - wp_set_password( $password, $user_id ); - $hash = wp_hash_password( $password ); - } + $check = false; + // If the hash is still md5 or otherwise truncated then invalidate it. + if ( strlen( $hash ) <= 32 ) { /** - * Filters whether the plaintext password matches the encrypted password. + * Filters whether the plaintext password matches the hashed password. * * @since 2.5.0 + * @since 6.8.0 Passwords are now hashed with bcrypt by default. + * Old passwords may still be hashed with phpass. * * @param bool $check Whether the passwords match. * @param string $password The plaintext password. * @param string $hash The hashed password. - * @param string|int $user_id User ID. Can be empty. + * @param string|int $user_id Optional ID of a user associated with the password. + * Can be empty. */ return apply_filters( 'check_password', $check, $password, $hash, $user_id ); } - /* - * If the stored hash is longer than an MD5, - * presume the new style phpass portable hash. - */ - if ( empty( $wp_hasher ) ) { + if ( ! empty( $wp_hasher ) ) { + // Check the password using the overridden hasher. + $check = $wp_hasher->CheckPassword( $password, $hash ); + } elseif ( strlen( $password ) > 4096 ) { + $check = false; + } elseif ( str_starts_with( $hash, '$wp' ) ) { + // Check the password using the current prefixed hash. + $password_to_verify = base64_encode( hash_hmac( 'sha384', $password, 'wp-sha384', true ) ); + $check = password_verify( $password_to_verify, substr( $hash, 3 ) ); + } elseif ( str_starts_with( $hash, '$P$' ) ) { + // Check the password using phpass. require_once ABSPATH . WPINC . '/class-phpass.php'; - // By default, use the portable hash from phpass. - $wp_hasher = new PasswordHash( 8, true ); + $check = ( new PasswordHash( 8, true ) )->CheckPassword( $password, $hash ); + } else { + // Check the password using compat support for any non-prefixed hash. + $check = password_verify( $password, $hash ); } - $check = $wp_hasher->CheckPassword( $password, $hash ); - /** This filter is documented in wp-includes/pluggable.php */ return apply_filters( 'check_password', $check, $password, $hash, $user_id ); } endif; +if ( ! function_exists( 'wp_password_needs_rehash' ) ) : + /** + * Checks whether a password hash needs to be rehashed. + * + * Passwords are hashed with bcrypt using the default cost. A password hashed in a prior version + * of WordPress may still be hashed with phpass and will need to be rehashed. If the default cost + * or algorithm is changed in PHP or WordPress then a password hashed in a previous version will + * need to be rehashed. + * + * Note that, just like wp_check_password(), this function may be used to check a value that is + * not a user password. A plugin may use this function to check a password of a different type, + * and there may not always be a user ID associated with the password. + * + * @since 6.8.0 + * + * @global PasswordHash $wp_hasher phpass object. + * + * @param string $hash Hash of a password to check. + * @param string|int $user_id Optional. ID of a user associated with the password. + * @return bool Whether the hash needs to be rehashed. + */ + function wp_password_needs_rehash( $hash, $user_id = '' ) { + global $wp_hasher; + + if ( ! empty( $wp_hasher ) ) { + return false; + } + + /** This filter is documented in wp-includes/pluggable.php */ + $algorithm = apply_filters( 'wp_hash_password_algorithm', PASSWORD_BCRYPT ); + + /** This filter is documented in wp-includes/pluggable.php */ + $options = apply_filters( 'wp_hash_password_options', array(), $algorithm ); + + $prefixed = str_starts_with( $hash, '$wp' ); + + if ( ( PASSWORD_BCRYPT === $algorithm ) && ! $prefixed ) { + // If bcrypt is in use and the hash is not prefixed then it needs to be rehashed. + $needs_rehash = true; + } else { + // Otherwise check the hash minus its prefix if necessary. + $hash_to_check = $prefixed ? substr( $hash, 3 ) : $hash; + $needs_rehash = password_needs_rehash( $hash_to_check, $algorithm, $options ); + } + + /** + * Filters whether the password hash needs to be rehashed. + * + * @since 6.8.0 + * + * @param bool $needs_rehash Whether the password hash needs to be rehashed. + * @param string $hash The password hash. + * @param string|int $user_id Optional. ID of a user associated with the password. + */ + return apply_filters( 'password_needs_rehash', $needs_rehash, $hash, $user_id ); + } +endif; + if ( ! function_exists( 'wp_generate_password' ) ) : /** * Generates a random password drawn from the defined set of characters. @@ -2865,6 +2991,7 @@ if ( ! function_exists( 'wp_set_password' ) ) : * of password resets if precautions are not taken to ensure it does not execute on every page load. * * @since 2.5.0 + * @since 6.8.0 The password is now hashed using bcrypt by default instead of phpass. * * @global wpdb $wpdb WordPress database abstraction object. * diff --git a/wp-includes/user.php b/wp-includes/user.php index 748efa5181..fe6f185680 100644 --- a/wp-includes/user.php +++ b/wp-includes/user.php @@ -205,7 +205,9 @@ function wp_authenticate_username_password( return $user; } - if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) { + $valid = wp_check_password( $password, $user->user_pass, $user->ID ); + + if ( ! $valid ) { return new WP_Error( 'incorrect_password', sprintf( @@ -219,6 +221,10 @@ function wp_authenticate_username_password( ); } + if ( wp_password_needs_rehash( $user->user_pass, $user->ID ) ) { + wp_set_password( $password, $user->ID ); + } + return $user; } @@ -282,7 +288,9 @@ function wp_authenticate_email_password( return $user; } - if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) { + $valid = wp_check_password( $password, $user->user_pass, $user->ID ); + + if ( ! $valid ) { return new WP_Error( 'incorrect_password', sprintf( @@ -296,6 +304,10 @@ function wp_authenticate_email_password( ); } + if ( wp_password_needs_rehash( $user->user_pass, $user->ID ) ) { + wp_set_password( $password, $user->ID ); + } + return $user; } @@ -445,7 +457,7 @@ function wp_authenticate_application_password( $hashed_passwords = WP_Application_Passwords::get_user_application_passwords( $user->ID ); foreach ( $hashed_passwords as $key => $item ) { - if ( ! wp_check_password( $password, $item['password'], $user->ID ) ) { + if ( ! WP_Application_Passwords::check_password( $password, $item['password'] ) ) { continue; } @@ -2431,6 +2443,7 @@ function wp_insert_user( $userdata ) { * * @since 4.9.0 * @since 5.8.0 The `$userdata` parameter was added. + * @since 6.8.0 The user's password is now hashed using bcrypt instead of phpass. * * @param array $data { * Values and keys for the user. @@ -2978,14 +2991,10 @@ function wp_get_password_hint() { * * @since 4.4.0 * - * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. - * * @param WP_User $user User to retrieve password reset key for. * @return string|WP_Error Password reset key on success. WP_Error on error. */ function get_password_reset_key( $user ) { - global $wp_hasher; - if ( ! ( $user instanceof WP_User ) ) { return new WP_Error( 'invalidcombo', __( 'Error: There is no account with that username or email address.' ) ); } @@ -3031,13 +3040,7 @@ function get_password_reset_key( $user ) { */ do_action( 'retrieve_password_key', $user->user_login, $key ); - // Now insert the key, hashed, into the DB. - if ( empty( $wp_hasher ) ) { - require_once ABSPATH . WPINC . '/class-phpass.php'; - $wp_hasher = new PasswordHash( 8, true ); - } - - $hashed = time() . ':' . $wp_hasher->HashPassword( $key ); + $hashed = time() . ':' . wp_fast_hash( $key ); $key_saved = wp_update_user( array( @@ -3063,9 +3066,7 @@ function get_password_reset_key( $user ) { * * @since 3.1.0 * - * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. - * - * @param string $key Hash to validate sending user's password. + * @param string $key The password reset key. * @param string $login The user login. * @return WP_User|WP_Error WP_User object on success, WP_Error object for invalid or expired keys. */ @@ -3074,8 +3075,6 @@ function check_password_reset_key( $key, $login ) { - global $wp_hasher; - $key = preg_replace( '/[^a-z0-9]/i', '', $key ); if ( empty( $key ) || ! is_string( $key ) ) { @@ -3092,11 +3091,6 @@ function check_password_reset_key( return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } - if ( empty( $wp_hasher ) ) { - require_once ABSPATH . WPINC . '/class-phpass.php'; - $wp_hasher = new PasswordHash( 8, true ); - } - /** * Filters the expiration time of password reset keys. * @@ -3118,7 +3112,7 @@ function check_password_reset_key( return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } - $hash_is_correct = $wp_hasher->CheckPassword( $key, $pass_key ); + $hash_is_correct = wp_verify_fast_hash( $key, $pass_key ); if ( $hash_is_correct && $expiration_time && time() < $expiration_time ) { return $user; @@ -3133,7 +3127,7 @@ function check_password_reset_key( /** * Filters the return value of check_password_reset_key() when an - * old-style key is used. + * old-style key or an expired key is used. * * @since 3.7.0 Previously plain-text keys were stored in the database. * @since 4.3.0 Previously key hashes were stored without an expiration time. @@ -3154,8 +3148,7 @@ function check_password_reset_key( * @since 2.5.0 * @since 5.7.0 Added `$user_login` parameter. * - * @global wpdb $wpdb WordPress database abstraction object. - * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. + * @global wpdb $wpdb WordPress database abstraction object. * * @param string $user_login Optional. Username to send a password retrieval email for. * Defaults to `$_POST['user_login']` if not set. @@ -4936,28 +4929,19 @@ All at ###SITENAME### * * @since 4.9.6 * - * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. - * * @param int $request_id Request ID. * @return string Confirmation key. */ function wp_generate_user_request_key( $request_id ) { - global $wp_hasher; - // Generate something random for a confirmation key. $key = wp_generate_password( 20, false ); - // Return the key, hashed. - if ( empty( $wp_hasher ) ) { - require_once ABSPATH . WPINC . '/class-phpass.php'; - $wp_hasher = new PasswordHash( 8, true ); - } - + // Save the key, hashed. wp_update_post( array( 'ID' => $request_id, 'post_status' => 'request-pending', - 'post_password' => $wp_hasher->HashPassword( $key ), + 'post_password' => wp_fast_hash( $key ), ) ); @@ -4969,8 +4953,6 @@ function wp_generate_user_request_key( $request_id ) { * * @since 4.9.6 * - * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. - * * @param string $request_id ID of the request being confirmed. * @param string $key Provided key to validate. * @return true|WP_Error True on success, WP_Error on failure. @@ -4980,8 +4962,6 @@ function wp_validate_user_request_key( #[\SensitiveParameter] $key ) { - global $wp_hasher; - $request_id = absint( $request_id ); $request = wp_get_user_request( $request_id ); $saved_key = $request->confirm_key; @@ -4999,11 +4979,6 @@ function wp_validate_user_request_key( return new WP_Error( 'missing_key', __( 'The confirmation key is missing from this personal data request.' ) ); } - if ( empty( $wp_hasher ) ) { - require_once ABSPATH . WPINC . '/class-phpass.php'; - $wp_hasher = new PasswordHash( 8, true ); - } - /** * Filters the expiration time of confirm keys. * @@ -5014,7 +4989,7 @@ function wp_validate_user_request_key( $expiration_duration = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); $expiration_time = $key_request_time + $expiration_duration; - if ( ! $wp_hasher->CheckPassword( $key, $saved_key ) ) { + if ( ! wp_verify_fast_hash( $key, $saved_key ) ) { return new WP_Error( 'invalid_key', __( 'The confirmation key is invalid for this personal data request.' ) ); } diff --git a/wp-includes/version.php b/wp-includes/version.php index 788d029fb6..5f6a71fd74 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.8-alpha-59827'; +$wp_version = '6.8-alpha-59828'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.