Make WordPress Core


Ignore:
Timestamp:
02/17/2025 11:22:33 AM (3 months ago)
Author:
johnbillion
Message:

Security: Switch to using bcrypt for hashing user passwords and BLAKE2b for hashing application passwords and security keys.

Passwords and security keys that were saved in prior versions of WordPress will continue to work. Each user's password will be opportunistically rehashed and resaved when they next subsequently log in using a valid password.

The following new functions have been introduced:

  • wp_password_needs_rehash()
  • wp_fast_hash()
  • wp_verify_fast_hash()

The following new filters have been introduced:

  • password_needs_rehash
  • wp_hash_password_algorithm
  • wp_hash_password_options

Props ayeshrajans, bgermann, dd32, deadduck169, desrosj, haozi, harrym, iandunn, jammycakes, joehoyle, johnbillion, mbijon, mojorob, mslavco, my1xt, nacin, otto42, paragoninitiativeenterprises, paulkevan, rmccue, ryanhellyer, scribu, swalkinshaw, synchro, th23, timothyblynjacobs, tomdxw, westi, xknown.

Additional thanks go to the Roots team, Soatok, Calvin Alkan, and Raphael Ahrens.

Fixes #21022, #44628

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/user.php

    r59817 r59828  
    206206    }
    207207
    208     if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
     208    $valid = wp_check_password( $password, $user->user_pass, $user->ID );
     209
     210    if ( ! $valid ) {
    209211        return new WP_Error(
    210212            'incorrect_password',
     
    220222    }
    221223
     224    if ( wp_password_needs_rehash( $user->user_pass, $user->ID ) ) {
     225        wp_set_password( $password, $user->ID );
     226    }
     227
    222228    return $user;
    223229}
     
    283289    }
    284290
    285     if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
     291    $valid = wp_check_password( $password, $user->user_pass, $user->ID );
     292
     293    if ( ! $valid ) {
    286294        return new WP_Error(
    287295            'incorrect_password',
     
    295303            '</a>'
    296304        );
     305    }
     306
     307    if ( wp_password_needs_rehash( $user->user_pass, $user->ID ) ) {
     308        wp_set_password( $password, $user->ID );
    297309    }
    298310
     
    446458
    447459    foreach ( $hashed_passwords as $key => $item ) {
    448         if ( ! wp_check_password( $password, $item['password'], $user->ID ) ) {
     460        if ( ! WP_Application_Passwords::check_password( $password, $item['password'] ) ) {
    449461            continue;
    450462        }
     
    24322444     * @since 4.9.0
    24332445     * @since 5.8.0 The `$userdata` parameter was added.
     2446     * @since 6.8.0 The user's password is now hashed using bcrypt instead of phpass.
    24342447     *
    24352448     * @param array    $data {
     
    29792992 * @since 4.4.0
    29802993 *
    2981  * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
    2982  *
    29832994 * @param WP_User $user User to retrieve password reset key for.
    29842995 * @return string|WP_Error Password reset key on success. WP_Error on error.
    29852996 */
    29862997function get_password_reset_key( $user ) {
    2987     global $wp_hasher;
    2988 
    29892998    if ( ! ( $user instanceof WP_User ) ) {
    29902999        return new WP_Error( 'invalidcombo', __( '<strong>Error:</strong> There is no account with that username or email address.' ) );
     
    30323041    do_action( 'retrieve_password_key', $user->user_login, $key );
    30333042
    3034     // Now insert the key, hashed, into the DB.
    3035     if ( empty( $wp_hasher ) ) {
    3036         require_once ABSPATH . WPINC . '/class-phpass.php';
    3037         $wp_hasher = new PasswordHash( 8, true );
    3038     }
    3039 
    3040     $hashed = time() . ':' . $wp_hasher->HashPassword( $key );
     3043    $hashed = time() . ':' . wp_fast_hash( $key );
    30413044
    30423045    $key_saved = wp_update_user(
     
    30643067 * @since 3.1.0
    30653068 *
    3066  * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
    3067  *
    3068  * @param string $key       Hash to validate sending user's password.
     3069 * @param string $key       The password reset key.
    30693070 * @param string $login     The user login.
    30703071 * @return WP_User|WP_Error WP_User object on success, WP_Error object for invalid or expired keys.
     
    30753076    $login
    30763077) {
    3077     global $wp_hasher;
    3078 
    30793078    $key = preg_replace( '/[^a-z0-9]/i', '', $key );
    30803079
     
    30913090    if ( ! $user ) {
    30923091        return new WP_Error( 'invalid_key', __( 'Invalid key.' ) );
    3093     }
    3094 
    3095     if ( empty( $wp_hasher ) ) {
    3096         require_once ABSPATH . WPINC . '/class-phpass.php';
    3097         $wp_hasher = new PasswordHash( 8, true );
    30983092    }
    30993093
     
    31193113    }
    31203114
    3121     $hash_is_correct = $wp_hasher->CheckPassword( $key, $pass_key );
     3115    $hash_is_correct = wp_verify_fast_hash( $key, $pass_key );
    31223116
    31233117    if ( $hash_is_correct && $expiration_time && time() < $expiration_time ) {
     
    31343128        /**
    31353129         * Filters the return value of check_password_reset_key() when an
    3136          * old-style key is used.
     3130         * old-style key or an expired key is used.
    31373131         *
    31383132         * @since 3.7.0 Previously plain-text keys were stored in the database.
     
    31553149 * @since 5.7.0 Added `$user_login` parameter.
    31563150 *
    3157  * @global wpdb         $wpdb      WordPress database abstraction object.
    3158  * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
     3151 * @global wpdb $wpdb WordPress database abstraction object.
    31593152 *
    31603153 * @param string $user_login Optional. Username to send a password retrieval email for.
     
    49374930 * @since 4.9.6
    49384931 *
    4939  * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
    4940  *
    49414932 * @param int $request_id Request ID.
    49424933 * @return string Confirmation key.
    49434934 */
    49444935function wp_generate_user_request_key( $request_id ) {
    4945     global $wp_hasher;
    4946 
    49474936    // Generate something random for a confirmation key.
    49484937    $key = wp_generate_password( 20, false );
    49494938
    4950     // Return the key, hashed.
    4951     if ( empty( $wp_hasher ) ) {
    4952         require_once ABSPATH . WPINC . '/class-phpass.php';
    4953         $wp_hasher = new PasswordHash( 8, true );
    4954     }
    4955 
     4939    // Save the key, hashed.
    49564940    wp_update_post(
    49574941        array(
    49584942            'ID'            => $request_id,
    49594943            'post_status'   => 'request-pending',
    4960             'post_password' => $wp_hasher->HashPassword( $key ),
     4944            'post_password' => wp_fast_hash( $key ),
    49614945        )
    49624946    );
     
    49694953 *
    49704954 * @since 4.9.6
    4971  *
    4972  * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
    49734955 *
    49744956 * @param string $request_id ID of the request being confirmed.
     
    49814963    $key
    49824964) {
    4983     global $wp_hasher;
    4984 
    49854965    $request_id       = absint( $request_id );
    49864966    $request          = wp_get_user_request( $request_id );
     
    50004980    }
    50014981
    5002     if ( empty( $wp_hasher ) ) {
    5003         require_once ABSPATH . WPINC . '/class-phpass.php';
    5004         $wp_hasher = new PasswordHash( 8, true );
    5005     }
    5006 
    50074982    /**
    50084983     * Filters the expiration time of confirm keys.
     
    50154990    $expiration_time     = $key_request_time + $expiration_duration;
    50164991
    5017     if ( ! $wp_hasher->CheckPassword( $key, $saved_key ) ) {
     4992    if ( ! wp_verify_fast_hash( $key, $saved_key ) ) {
    50184993        return new WP_Error( 'invalid_key', __( 'The confirmation key is invalid for this personal data request.' ) );
    50194994    }
Note: See TracChangeset for help on using the changeset viewer.