wordpress-hash-node icon indicating copy to clipboard operation
wordpress-hash-node copied to clipboard

Need update to Support WordPress moving to bcrypt

Open styledev opened this issue 7 months ago • 2 comments

See https://make.wordpress.org/core/2025/02/17/wordpress-6-8-will-use-bcrypt-for-password-hashing/

The wp_hash_password() and wp_check_password() functions have been updated to use the PHP native password_hash() and password_verify() functions with the bcrypt algorithm and SHA-384 pre-hashing. Both functions retain support for the $wp_hasher global object in case that’s being used to implement an alternative hashing mechanism.

The wp_check_password() function retains support for passwords that were hashed using phpass, which means existing password hashes won’t be invalidated.

A new wp_password_needs_rehash() function has been introduced as a wrapper for password_needs_rehash(). If a plugin needs to adjust its logic then the password_needs_rehash filter can be used. The function is also pluggable, so it can be overridden if absolutely necessary.

Pre-hashing with SHA-384 is implemented in order to avoid the 72 byte length limit imposed on passwords by bcrypt. Password hashes are therefore stored with a $wp prefix to distinguish them from vanilla bcrypt hashes which may be in use via a plugin. By default this means the full prefix will be $wp$2y$.

styledev avatar May 28 '25 21:05 styledev

so how do we replace this wordpress-hash-node with existing bcrypt hasher? i try to use bcrypt package but it doesn't work

import bcrypt from 'bcrypt'
bcrypt.compare(body.password, db.user.userPass)

i already try to remove the "$wp$2y$" prefix but it still didn't match, i also try to replace the "$wp$2y$" prefix with "$2a$12$" but it still also didn't match.

volfadar avatar Sep 08 '25 06:09 volfadar

Okay, finally found the correct one. First, you should use "bcryptjs" instead of "bcrypt". It’s true that it has worse performance, but who cares, right? This is just for login purposes anyway.

Then, write your code like this:

import hasher from 'wordpress-hash-node'
import bcryptjs from 'bcryptjs'
import { createHmac } from 'node:crypto'
  // Two-strategy password verification: Primary (wordpress-hash-node) + Fallback (WordPress 6.8+ bcryptjs)
  let passwordValid = false

  try {
    // Primary strategy: wordpress-hash-node for legacy WordPress passwords
    passwordValid = hasher.CheckPassword(password, user.userPass)
    if (!passwordValid) throw new Error('Primary strategy failed, trying fallback')
  } catch (error) {

    // Fallback strategy: WordPress 6.8+ with SHA-384 pre-hashing + bcryptjs
    if (user.userPass.startsWith('$wp$2y$')) {
      const trimmedPassword = password.trim()

      // WordPress 6.8+ uses SHA-384 pre-hashing before bcrypt
      const hmac = createHmac('sha384', 'wp-sha384')
      hmac.update(trimmedPassword)
      const preHash = hmac.digest('base64')

      const bcryptHash = user.userPass.substring(3) // Remove '$wp' prefix
      passwordValid = bcryptjs.compareSync(preHash, bcryptHash)
    } 
  }

  if (!passwordValid) {
    console.log('Invalid password')
    throw new Error('Invalid password')
  }

volfadar avatar Sep 08 '25 07:09 volfadar