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.
Related
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.
from laravel docs
Application Key The next thing you should do after installing Laravel
is set your application key to a random string. If you installed
Laravel via Composer or the Laravel installer, this key has already
been set for you by the php artisan key:generate command.
Typically, this string should be 32 characters long. The key can be
set in the .env environment file. If you have not renamed the
.env.example file to .env, you should do that now. If the application
key is not set, your user sessions and other encrypted data will not
be secure!
What I know about application key is: If the application key is not set, generally I do get an exception.
How do this random string help to secure the session?
What are the other uses of this application key?
If I use the same application key everywhere (like staging, production etc..) does it make the application less secure?
what are some best practices for this key
As we can see its used in EncryptionServiceProvider:
public function register()
{
$this->app->singleton('encrypter', function ($app) {
$config = $app->make('config')->get('app');
// If the key starts with "base64:", we will need to decode the key before handing
// it off to the encrypter. Keys may be base-64 encoded for presentation and we
// want to make sure to convert them back to the raw bytes before encrypting.
if (Str::startsWith($key = $this->key($config), 'base64:')) {
$key = base64_decode(substr($key, 7));
}
return new Encrypter($key, $config['cipher']);
});
}
So every component that uses encryption: session, encryption (user scope), csrf token benefit from the app_key.
Rest of the questions can be answered by "how encryption" (AES) works, just open up Encrypter.php, and confirm that Laravel uses AES under the hood and encodes the result to base64.
Further more we can see how its all done by using tinker:
➜ laravel git:(staging) ✗ art tinker
Psy Shell v0.8.17 (PHP 7.1.14 — cli) by Justin Hileman
>>> encrypt('Hello World!')
=> "eyJpdiI6ImgzK08zSDQyMUE1T1NMVThERjQzdEE9PSIsInZhbHVlIjoiYzlZTk1td0JJZGtrS2luMlo0QzdGcVpKdTEzTWsxeFB6ME5pT1NmaGlQaz0iLCJtYWMiOiI3YTAzY2IxZjBiM2IyNDZiYzljZGJjNTczYzA3MGRjN2U3ZmFkMTVmMWRhMjcwMTRlODk5YTg5ZmM2YjBjMGNlIn0="
Note: I used this key: base64:Qc25VgXJ8CEkp790nqF+eEocRk1o7Yp0lM1jWPUuocQ= to encrypt Hello World!
After decoding the result we get (you can try decode your own cookie with session):
{"iv":"h3+O3H421A5OSLU8DF43tA==","value":"c9YNMmwBIdkkKin2Z4C7FqZJu13Mk1xPz0NiOSfhiPk=","mac":"7a03cb1f0b3b246bc9cdbc573c070dc7e7fad15f1da27014e899a89fc6b0c0ce"}
to understand above json (iv, value, mac) you need to understand AES:
https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
Best practices for application key
do store it in .env file only
do not store it in app.php, in fact in any git tracked file
do not change it unless you really want to
invalidate sessions/cookies (user logout)
invalidate password reset tokens
invalidate signed urls
Obvious Note: Changing application key has no effect on hashed passwords since hashing algorithms do not require encryption keys.
We are building application where we need to store a data encrypted in database and instead of using MySql AES_ENCRYPT and AES_DECRYPT we are plaining to use laravel's inbuilt encrypt & decrypt functions.
Is it will be future proof as we don't want to loose data for future updates.
First of all, nothing is truly "future proof." In fact, we're on the verge of current encryption being rendered obsolete by quantum computing, making all current encryption methods very much not future proof.
Does Taylor have any plans of changing it in the foreseeable future? Maybe, maybe not, but the only real way of knowing is to ask him directly. He's quite active on Twitter and in other venues, so as far as business owners go, he's pretty approachable. He's also a generally nice person, so don't be afraid to ping him.
But let's take a look at the code:
public function encrypt($value, $serialize = true)
{
$iv = random_bytes(16);
// First we will encrypt the value using OpenSSL. After this is encrypted we
// will proceed to calculating a MAC for the encrypted value so that this
// value can be verified later as not having been changed by the users.
$value = \openssl_encrypt(
$serialize ? serialize($value) : $value,
$this->cipher, $this->key, 0, $iv
);
if ($value === false) {
throw new EncryptException('Could not encrypt the data.');
}
// Once we get the encrypted value we'll go ahead and base64_encode the input
// vector and create the MAC for the encrypted value so we can then verify
// its authenticity. Then, we'll JSON the data into the "payload" array.
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'));
if (! is_string($json)) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
That's the main encrypt() function from master in the repository, and from the looks of it, it's not likely to be changed too much without completely rewriting it. And while Laravel doesn't really follow the SemVer versioning spec, it does generally follow an internally consistent versioning scheme, making the most likely times for it to change are at the whole number and first-decimal change (i.e. - 5.4 to 5.5 or 5.5 to 6.0).
However, it's worth noting that it's actually accessed via contracts and the service provider pattern (so the only time the class is actually directly referenced is in its associated ServiceProvider class). This means that you can use this one for now and if a breaking change is introduced in the future, you can copy this version into your own encryption class, replace the reference in config/app.php to Illuminate\Encryption\EncryptionServiceProvider to your new encryption service provider, and you've now preserved that method and can use it throughout your application, without making any other changes to your application.
On a bit of a side note, you can also consider writing an "encryption converter" if you find you do need to change algorithms (such as if your original algorithm is insecure) by using the old system's decrypt method to decrypt everything, then re-encrypt it all with the new system and storing it again. The application would then just use the new algorithm going forward.
I am building an iOS app for an already existing web application I created. The web app uses laravel and sentry to encrypt passwords. New users have to be able to be created from the iOS app.
The server that the web app talks to is written in php but does not use laravel or sentry.
The only sentry function I need is the one they use to encrypt passwords.
What function does sentry use to hash passwords? I am talking about Cartalyst\Sentry\Hashing\NativeHasher
I need to be able to duplicate this function and use it in a separate php file.
i've found this link : https://github.com/cartalyst/sentry/blob/master/src/Cartalyst/Sentry/Hashing/NativeHasher.php
and this code is what you want probably:
public function hash($string)
{
// Usually caused by an old PHP environment, see
// https://github.com/cartalyst/sentry/issues/98#issuecomment-12974603
// and https://github.com/ircmaxell/password_compat/issues/10
if (!function_exists('password_hash')) {
throw new \RuntimeException('The function password_hash() does not exist, your PHP environment is probably incompatible. Try running [vendor/ircmaxell/password-compat/version-test.php] to check compatibility or use an alternative hashing strategy.');
}
if (($hash = password_hash($string, PASSWORD_DEFAULT)) === false) {
throw new \RuntimeException('Error generating hash from string, your PHP environment is probably incompatible. Try running [vendor/ircmaxell/password-compat/version-test.php] to check compatibility or use an alternative hashing strategy.');
}
return $hash;
}
A requirement for the deployment of a PHP application I am working on is that is uses FIPS-140 validated cryptographic modules.
The customer has specifically flagged up that "PHP utilizes a cryptographically weak random number generator to produce session ID information" and cited this report: http://berlin.ccc.de/~andreas/php-entropy-advisory.txt
I have advised them on how to set session.entropy_length and session.hash_function to increase entropy, but they have not accepted this, specifically requiring that we use a FIPS-140 compliant RNG.
I'm not certain on the difference between the hash function and the RNG, so am struggling to respond. Can anyone suggest a way of using a FIPS-140 compliant function to generate session ids within php?
We're running PHP 5.4.16 on Windows + SQL Server, in case it matters.
Thanks
A requirement for the deployment of a PHP application I am working on is that is uses FIPS-140 validated cryptographic modules.
My condolences. A headache is to FIPS-140 what a drop of morning dew is to the ocean.
I'm not certain on the difference between the hash function and the RNG, so am struggling to respond. Can anyone suggest a way of using a FIPS-140 compliant function to generate session ids within php?
If you're using ext/mcrypt, mcrypt_create_iv() uses Windows' CryptGenRandom API which should be FIPS-140 compliant. (Or, at minumum, it should be possible to setup that way.) That function is the only good thing about mcrypt, and exists separatef from libmcrypt.
If you're using OpenSSL, and compiled OpenSSL in FIPS mode, you can similarly use openssl_random_pseudo_bytes() and it should use a FIPS-compliant generator.
Finally, if you upgrade to PHP 7+ and use random_bytes(), so long as Windows is FIPS-140 compliant, you're golden.
The hash function really doesn't matter here. You want to use a secure source and that's it. Hashing it doesn't buy you anything. If you're forced to use a hash function, use one of the SHA2 family hash functions (SHA256, SHA384, or SHA512) approved for use in FIPS-140 compliant software.
Session Generator that should pass FIPS-140 audits
<?php
/**
* #return string
*/
function session_id_fips140()
{
if (is_callable('random_bytes')) {
return session_id(bin2hex(random_bytes(32)));
}
if (is_callable('mcrypt_create_iv')) {
return session_id(bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)));
}
if (is_callable('openssl_random_pseudo_bytes')) {
return session_id(bin2hex(openssl_random_pseudo_bytes(32)));
}
// Fail closed. Maybe install random_compat?
throw new Exception("No suitable PRNG is available on the current system!");
}
Usage:
<?php
ini_set('session.
if (!isset($_COOKIE['PHPSESSID'])) {
session_id_fips140();
}
session_start();