Laravel 9 is rejecting a valid password hashed with bcrypt - php

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?

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.
*/
?>

I have a call to Hash::make in the observer for the user class. I discovered that it was running even though it wasn't supposed to, resulting in a duplicate hash.

Related

Calling codeigniter encryption key

I have set the encryption key in config.php file
$config['encryption_key'] = 'az2x#_.#!`~$aezxqy+=#%^&';
I want to use this key in password when registering users into the website, right now i am using this method
'password' => hash('sha256', $password . 'az2x#_.#!`~$aezxqy+=#%^&')
Is there any way i can set the above code like
'password' => hash('sha256', $password . $config['encryption_key']),
when i do it gives error of undefined $config variable.
I strongly suggest using the built-in php API for password salt/hash and not the method you are using, the encryption key is meant to be used for the encryption class...
$passwordHash = password_hash($password, PASSWORD_BCRYPT);
You use this salted and hashed password stored in the database.
Then verify it when getting it from the DB using:
password_verify($password, $result->password)
This returns a BOOLEAN so you use it in an if statement:
if ( password_verify($password, $result->password) ) {
// do login stuff
} else {
// handle login failure
}
Else, you can use the encryption key by the following (strongly suggest against it):
$this->config->item('encryption_key');
If this doesn't work you are probably writing a helper or something, so you'll need to call the CI instance to get access to the config array.

Symfony 2 - not authenticating user using bcrypt

I add a user to the database.
I encode their password using bcrypt encryption:
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword($user->getPassword(),$user->getSalt());
$postData->setPassword($password);
This all persists to the database just fine.
On my User Entity my getSalt() method returns null:
public function getSalt(){
return null;
}
Now when I try to authenticate the symfony logs tells me that I am entering bad credentials.
However...
If i use this site to encrypt my password with bcrypt:
http://bcrypthashgenerator.apphb.com/
Then enter the result into the database manually and try and authenticate with the users password i have updated with the results from the above link,
It works fine.
So there is something I have not set, that when I am encrypting the password that symfony cannot authenticate against it.
I have tried the setting a salt on the getSalt(), setting it to an empty string.
EDIT
I am using php 5.5.9
I have also tried using the BCryptPasswordEncoder($cost) class,
setting the cost on the __construct the same as what's in my security.yml
and it still does not authenticate.
EDIT
I have also tried installing this class via composer to as suggested here.
EDIT
If I use the php method:
password_hash('testertester',PASSWORD_BCRYPT,array('cost'=>13));
It authenticates perfectly.
So its something to do deeper in Symfony. I'll keep digging.
Still no luck :-/
I am scouring the web to no avail so far.
Any help would be greatly appreciated.
Ad
I was passing in the existing entity object not the submitted data object!!
$postData = $form->getdata();
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword($user->getPassword(),$user->getSalt());
$postData->setPassword($password);
Note this line here:
$user->getPassword() needed to be $postData->getPassword()
$password = $encoder->encodePassword($postData->getPassword(),$user->getSalt());
You live and learn ay!

PHP password_hash always contains hashkey

I am trying to test the password_hash method for this purpose i have created the following function hashPassword:
function hashPassword($string) {
$settings = array('cost' => 10, 'encryption_key' => 'thisIsMyEncryptionKey1234');
return password_hash($string, PASSWORD_BCRYPT, $settings);
}
Now if i test this with a random string like "test"
The result would be:
$2y$10$thisIsMyEncryptionKeyu5n3NNnKh3DjgJqgb5pE8YOLBclKrVWC
Or if i test it with helloworld:
$2y$10$thisIsMyEncryptionKeyuVw8QRVNw8HbEWHX2oQlArVtne2TzOpS
Can anyone tell me why this is happening? Or is it suppose to be like this?
You should never provide the encryption key manually unless you have a very good reason to do so. I'd recommend reading the docs on password_hash some more.
Proper usage just lets the system figure it all out on its own:
function hashPassword($password)
{
return password_hash($password, PASSWORD_DEFAULT);
}
PHP will then internally choose the best available algorithm and most fitting number of iterations for current hardware, and generate a safe and unique salt.
To validate the password, then use password_verify, and check for required rehashes, for example in a User class:
class User
{
...
public function verifyPassword($password)
{
if(!password_verify($password, $this->hash))
return false;
if(password_needs_rehash($this->hash, PASSWORD_DEFAULT))
$this->setNewHashAndSaveToDB(password_hash($password, PASSWORD_DEFAULT));
return true;
}
}
By using this construct, you ensure hashed passwords are always kept up to date and secure as hardware capacities progress, automatically when a user logs in.
The policy on what algorithm PASSWORD_DEFAULT chooses, and with which config, is as follows:
Updates to supported algorithms by this function (or changes to the
default one) must follow the follwoing rules:
Any new algorithm must be in core for at least 1 full release of PHP prior to becoming default. So if, for example, a new algorithm
is added in 5.5.5, it would not be eligible for default until 5.7
(since
5.6 would be the first full release). But if a different algorithm was added in 5.6.0, it would also be eligible for default at 5.7.0.
The default should only change on a full release (5.6.0, 6.0.0, etc) and not on a revision release. The only exception to this is
in an emergency when a critical security flaw is found in the
current default.
About Encryption key:
Best Encrуption kеy is a binary blob that's gеnеrated from a rеliablе random numbеr gеnеrator. Thе following еxample would bе rеcommеndеd (>= 5.3):
$keySize = mcrypt_get_key_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CFB);
$encryptionKey = openssl_random_pseudo_bytes($key_size, $strong); //$strong will be true if the key is crypto safe
But in your case you just set the string, use some random data for this.

How to Use Salt Generated in PHP in Grails Project

I have a PHP application I'm converting to Grails. The PHP application used a salt-mechanism to encode the passwords of all its users.
When moving the salt and salted password into the (custom) user database in Grails, I am unable to log in while in my Grails application.
Of course, I'm using the Spring Security Core plugin, and I've added the salt to the User domain class as specified in this tutorial, which I found here: Grails with Spring Security Plugin and Salted Passwords
After running through the tutorial, I am able to add a user and successfully log in with that user:
[BootStrap.groovy]:
new User( username:"user", email:"user#place.com", password:"password", enabled:true).save(flush: true)
(you might also notice the addition of email, which I added using this tutorial)
But I am unable to login using any of the users that were transferred over from the PHP project. If it's any help, here's how their passwords were encoded:
$password = "password";
$salt = bin2hex( openssl_random_pseudo_bytes( 32 ) );
$passwordSalted = hash( "sha256", $salt . $password );
Looks like Burt nailed it with his suggestion here: http://grails.1312388.n4.nabble.com/Spring-Security-Core-plugin-and-multiple-salt-sources-tc3638236.html#a3646256
Basically, I already had my own salt provided from the tutorials I mentioned above, I just needed to combine it with the password using a custom password encoder.
public class CustomPasswordEncoder extends MessageDigestPasswordEncoder {
public CustomPasswordEncoder() {
super("SHA-256");
}
#Override
protected String mergePasswordAndSalt(String password, Object salt, boolean strict) {
if (password == null) {
password = "";
}
if (salt == null || "".equals(salt)) {
return password;
}
return salt + password;
}
}

mobile app development - how to create a server implementation

EDIT Originally I thought Oauth2 is the way to go but maybe it is not. I'll leave that out of this question for now as it is confusing things.
I'm creating a mobile app (Android/iOS). I'd like the user to enter their credentials (user/pass) in the mobile device which would then get sent to my server (Joomla CMS) to verify the credentials and create/send a token. I don't want to store the user/pass on the device just the token.
In addition this token needs to have a timeout to be refreshed when needed. Such as credentials have changed.
At this point I'm trying to figure out what the architecture of this will look like.
Are there any tutorials on how you can achieve this (ideally with Joomla)? Anything that someone could point me to?
You should post the username and password from the mobile app and from there on you should follow the solution provided in this question:
https://stackoverflow.com/a/2188969/900617
The end solution is to create my own Joomla component. Pretty much everything is in my controller. Not the final code but something like this will work.
defined('_JEXEC') or die;
jimport('joomla.application.component.controller');
class FooauthController extends JController
{
function __construct() {
// params
$jinput = JFactory::getApplication()->input;
$this->username = $jinput->get('user', '', 'STRING');
$this->password = $jinput->get('password', '', 'STRING');
$this->checkParameters();
}
private function checkParameters() {
// datatype checks
if ($this->username == '' || $this->password == '') {
header('HTTP/1.1 400 Bad Request', true, 400);
}
}
private function createToken() {
// token generation - what Joomla does (just an example)
jimport('joomla.user.helper');
$salt = JUserHelper::genRandomPassword(32);
$crypted = JUserHelper::getCryptedPassword($password, $salt);
$cpassword = $crypted.':'.$salt;
return $cpassword;
}
function execute() {
// Get the global JAuthentication object
jimport( 'joomla.user.authentication');
$auth = & JAuthentication::getInstance();
$credentials = array( 'username' => $this->username, 'password' => $this->password );
$options = array();
$response = $auth->authenticate($credentials, $options);
// success
if ($response->status === JAUTHENTICATE_STATUS_SUCCESS) {
$response->status = true;
echo json_encode($this->createToken());
} else {
// failed
$response->status = false;
echo json_encode($response);
}
}
}
This represents a component called com_fooauth. Now the native app will send a query like this:
http://www.myhost.com/index.php?option=com_fooauth&user=username&password=pass&format=raw
Kind of a short cut to put everything in the controller, but hopefully you get the idea.
I hope that I understand correctly your use case.
If you want to use oAuth, then your mobile apps are considered as the oAuth-client.
Your "server" holds the "protected resources", and it can be used only with oAuth access-token, so it is called "resource server". Now you want something to supply this access-token, so this is the identity-provider, AKA authentication server, e.g. Facebook, Google, (or implement one by your own).
The flow is (generally): the user (mobile app) tries to reach a protected resource; since it has no token, he is being redirected to the auth-server. the latter is responsible for the user/password login page, and creating the token.
If it is true - you still can implement everything by your own, without using Facebook/Google APIs, because oAuth has SPECs. However, it can be easier for you to use the providers' packages.
EDIT: reconsider the usage of oAuth
You use oAuth only if you want your webapp to support oAuth SPEC. There are several benefits, one of them is that you can use 3rd party identity provider, e.g. Yahoo! and use their identities without managing them. So if I have a user in Yahoo!, I can use your app without additional registrations (your app will have to support access-tokens from Yahoo!). But in your case, you are about to implement all the logic of identity-provider (forgot password, change password, registration, etc) plus supporting oAuth - and all of this without enjoying the benefits of oAuth at all! So - you have to reconsider the usage of oAuth...
You need to use their APIs as a base. They aren't going to just let you build your own API that connects to their database, that to them would look more like a password cracker than an API.
This isn't Joomla or a tutorial, (and I'm very rusty in php) that said...
First a few caveats:
* memcache isn't secure & this implementation has you putting username / password in: Be sure that it is safely behind a firewall, or else encrypt it first. Happy to give some pointers on that if you need it.
* memcache isn't guaranteed not to drop data if it runs out of memory. In practice it is reliable, but your app should handle that gracefully. If you don't want to lose data like that, just substitute something like couchbase for memcache.
* just returning a token in response to a login probably isn't super useful. I'd json-ize the token along with stuff like the user name, and any other info to get the app up and running without needing to make a second API call.
* the code below doesn't handle error cases, I can call them all out in more detail if that isn't obvious to you.
If it were me, I'd just use memcache to persist the tokens & map that token to the username & password that was originally passed. You can use the memcache time to live to get your expiration for free.
Send username / password to the server (ideally over https).
Create a random string or guid (eg: http://php.net/manual/en/function.uniqid.php or http://www.lateralcode.com/creating-a-random-string-with-php/) , this is your token
Store the username / password in memcache with that token as a key
Set a timeout
$token = createToken("user1234", "pass2324");
print "Token: $token \n\n";
$credentials = credtialsFromToken($token);
print "Credentials from the token: ";
var_dump($credentials);
print "\n\n";
function setup() {
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");
}
function createToken($user, $pass) {
$TOKEN_EXPIRE_TIME=60 * 60 * 24 * 30;
$credentials = array(
"user" => $user,
"pass" => $pass,
);
$token = uniqid( );
memcache_set($token, credentials, 'some variable', 0, 30);
return $token;
}
function credtialsFromToken($token) {
$credentials = memcache_get($token);
return $credentials;
}
If the token is incorrect or expired, they get an null credentials back and have to login.
Edit: cleaned it up into functions that appear to work in php...

Categories