Home > Net >  Laravel 9 is rejecting a valid password hashed with bcrypt
Laravel 9 is rejecting a valid password hashed with bcrypt

Time:01-31

I've been spending a few days troubleshooting a failure of certain passwords to validate in Laravel 9. The password testperson resolves to the hash $2y$10$5xc/wAmNCKV.YhpWOfyNoetCj/r3Fs5TyAskgZuIF/LEItWfm7rPW. A direct query on the corresponding database table confirms that this is the correct hash. Yet Laravel's authentication infrastructure rejects this password and denies authentication.

This is not universal. I have multiple passwords that are resolving correctly. For example, the password eo resolves to $2y$10$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2, and Laravel authenticates that password. The same mechanism creates both of these user records, though they have different permissions (indicated by boolean values on the record).

I tracked down the bug to the function password_verify, which was identified as returning false negatives in this Stack Overflow question and this Treehouse thread.

Specifically, here is the stack in Laravel that gets down to this failure point:

  • The login route calls \Illuminate\Foundation\Auth\AuthenticatesUsers::login via the controller class.
  • The login method calls \Illuminate\Foundation\Auth\AuthenticatesUsers::attemptLogin.
  • The attemptLogin method calls the attempt method of the controller's guard object.
  • \Illuminate\Auth\SessionGuard::attempt calls \Illuminate\Auth\SessionGuard::hasValidCredentials.
  • \Illuminate\Auth\SessionGuard::hasValidCredentials calls the validateCredentials method on the guard's provider object.
  • Illuminate\Auth\EloquentUserProvider::validateCredentials calls the check method on its hasher object.
  • Illuminate\Hashing\HashManager::check calls the check method on its driver.
  • Illuminate\Hashing\BcryptHasher::check calls Illuminate\Hashing\AbstractHasher::check.
  • Illuminate\Hashing\AbstractHasher::check calls password_verify.

After unwinding this entire stack, I ran the following code in the login method of the login controller:

$provider = $this->guard()->getProvider();
$credentials =  $this->credentials($request);
$user = $provider->retrieveByCredentials($credentials);
$password_unhashed = $request['password'];
$password_hashed = $user->getAuthPassword();
$password_verify = password_verify($password_unhashed, $password_hashed);
logger('attemping login', compact('password_verify','password_unhashed','password_hashed'));

That dumps this context:

{
"password_verify": false,
"password_unhashed": "testperson",
"password_hashed": "$2y$10$5xc/wAmNCKV.YhpWOfyNoetCj/r3Fs5TyAskgZuIF/LEItWfm7rPW"
}

And if I put that password into a SELECT users WHERE password= query, I get the user that I'm expecting.

What's going on here? And how do I get around this?

CodePudding user response:

It seems that Laravel's authentication infrastructure is not accepting the password hash for "testperson" because of a false negative from the PHP function password_verify. This function is used in the authentication process to compare the user-entered password with the stored password hash.

You can try to work around this issue by checking for common causes of false negatives in password_verify, such as incorrect hash algorithm settings or incorrect use of salt. Make sure the settings and parameters being used by Laravel match the ones used when the password hash was created.

If the issue persists, you could also try using a different hashing algorithm or library to store and compare the passwords.

CodePudding user response:

I think your assertion that the hash you provided is a hash of 'testperson' is in fact false. Since hashing is one-way, I can't tell you what the hash you showed is derived from. NOTE: This runs on PHP 7.4, but I don't think it will work on PHP 8 and beyond because of the deprecation of the salt option in password_hash().

<?php
//$testhash = '$2y$10$5xc/wAmNCKV.YhpWOfyNoetCj/r3Fs5TyAskgZuIF/LEItWfm7rPW';
$testhash = '$2y$10$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2';
//$password = "testperson";
$password = "eo";
$options = array("cost" => 10, "salt" => substr($testhash, 7, 22));
$pwhash = password_hash($password, PASSWORD_BCRYPT, $options);
echo $pwhash."\n";
$salt = substr($pwhash, 0, 29);
echo $salt."\n";
$cryptpw = crypt($password, $salt);
echo $cryptpw."\n";
if (password_verify($password, $cryptpw)) {
  echo("Verified.\n");
} else  {
  echo("NOT Verified.\n");
}
if (password_needs_rehash($cryptpw, PASSWORD_BCRYPT, $options)) {
  echo("Needs rehash.\n");
} else {
  echo("Doesn't need rehash.\n");
}

/*
testperson results...
$2y$10$5xc/wAmNCKV.YhpWOfyNoeVNPMEcYrxepQeFAssFoAaIYs4WLmgZO
$2y$10$5xc/wAmNCKV.YhpWOfyNoe
$2y$10$5xc/wAmNCKV.YhpWOfyNoeVNPMEcYrxepQeFAssFoAaIYs4WLmgZO
Verified.
Doesn't need rehash.

eo results...
$2y$10$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2
$2y$10$uNWYvMVmagIwQ2eXnVKLCO
$2y$10$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2
Verified.
Doesn't need rehash.
*/
?>
  • Related