I am trying to verify whether a string matches a SHA-512 (Unix) hash ($6$..) in PHP. Much like the password_verify() function for Blowfish (BCrypt) but for SHA-512 (Unix) instead.
I stumbled upon hash_equals which tends to compare two strings using the same time. However, I am unable to get the expected output boolean true on the following:
<?php
$expected = '$6$9e87b0c78da9ab83$5V16BLuWUkoG3g1oH3kwhs8rzBpjydUps1qBXuY3PkkFzDSjqklT47L5pmG8JPqDRDk.ZTJoS/ogtHkyXC2L40';
if (CRYPT_SHA512 == 1) {
$correct = crypt('OkvraMADvua', '$6$12$usesomesillystringforsalt$');
}
var_dump(hash_equals($expected, $correct));
?>
Right now, I get boolean false even though the hash value of $expected corresponds to the plaintext and the hash generated for the $correct variable also matches that same plaintext (OkvraMADvua).
The issue is that the salt is always different and that is to be expected with crypt(3) algorithms. Whenever I use the same salt, it's evident that the output would be boolean true.
Related
Just before using password_hash(), I check if PASSWORD_DEFAULT === PASSWORD_BCRYPT to see if I need to do some cleanup on the password before it's hashed (Argon2 won't need this).
I'm simply passing it though a quick hash first, because bcrypt has an issue with NULL characters, and passwords longer than 72 characters (more info, and a different example).
But in PHP 7.4, the constant PASSWORD_DEFAULT is now set to NULL.
So how can I tell what algorithm password_hash() will use?
This is an interesting problem, for which I believe there is no standard function yet. This is not a huge problem because the hash itself contains an identifier telling us which hash algorithm was used. The important thing to note here is that PASSWORD_DEFAULT is a constant. Constants do not change.
To figure out which algorithm is used when using the default constant (which was and still is bcrypt), you need to generate some dummy hash and look at the beginning of it. We can even use a nice helper function password_get_info()
$hashInfo = password_get_info(password_hash('pass', PASSWORD_DEFAULT, [ 'cost' => 4 ] ));
echo $hashInfo['algo']; // should return either 1 or 2y
if($hashInfo['algo'] === PASSWORD_BCRYPT) {
// will be true for PHP <= 7.4
}
Edit
As of PHP 7.4.3 you can continue using PASSWORD_DEFAULT === PASSWORD_BCRYPT
https://3v4l.org/nN4Qi
You don't actually have to use password_hash twice. A better and faster way is to provide an already hashed value with Bcrypt and check it against PASSWORD_DEFAULT with
password_needs_rehash function to see if the default algo has changed or not.
bcrypt algorithm is the default as of PHP 5.5.0
So for example:
$hash = '$2y$10$ra4VedcLU8bv3jR0AlpEau3AZevkQz4Utm7F8EqUNE0Jqx0s772NG'; // Bcrypt hash
// if it doesn't need rehash then the default algo is absolutely Bcrypt
if (! password_needs_rehash($hash, PASSWORD_DEFAULT)) {
// do some clean up
}
Note: make sure that the hash value($hash) has the same cost provided in password_needs_rehash's third parameter, otherwise it will consider the hash outdated and need rehash since the cost has changed.
Does anyone have an idea about how the password_verify() function works? I've searched everywhere in the net about the said function , but I never found a specific answer on how it compares its two parameters. Below is the proper syntax of the said function according to php.net:
bool password_verify ( string $password , string `$hash` )
The question is, does the function hashes the $password then compared it to $hash? or
It dehashed $hash instead then compare it with $password?
Since the entire point of a hash function is that it cannot be reversed, password_verify can't be using option 2.
That leaves option 1.
You can also look at the source code where you can see that …
zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
… it crypts the password and then …
/* We're using this method instead of == in order to provide
* resistance towards timing attacks. This is a constant time
* equality check that will always check every byte of both
* values. */
for (i = 0; i < ZSTR_LEN(hash); i++) {
status |= (ZSTR_VAL(ret)[i] ^ ZSTR_VAL(hash)[i]);
}
… compares the hashed password (ret) with the hashed value passed in (hash)
There's no such thing as "dehashing". A hash is a one way function.
What password_verify actually does is, reading the salt and hashing function from the "hash", given by password_hash and then do exactly the same hashing again, with the given parameters.
Because of that, it's important to use password_verify, instead of just do something like $hash == password_hash('...'), as password_hash could use anothere hashing algorithm and creates each time a new random salt value. So calling password_hash with the same input multiple times on the same machine, would never return the same value.
When we use CRYPT() method of any variable.
$test = 'password';
echo CRYPT($test);
Result
$1$g9s9ZdtF$sBBiBc4PdljOL4sDLx4CK.
When we use MD5() method of same variable.
$test = 'password';
echo MD5($test);
Result
5f4dcc3b5aa765d61d8327deb882cf99
Now, what is difference in both Answer?
crypt() is a function that creates password hashes from plaintext and a (randomly generated or provided) salt value. It can use several underlaying hash algorithms like DES, MD5, Blowfish or SHA.
Using a "salted" password means that the same plaintext input does not always produce the same hash. So you can't say "I've seen this hash before it's the one for 'abc123'".
As it's available in libc there are crypt() functions in almost all programming languages and database servers so its very interoperable.
How secure it is only depends on the hash algorithm you chose (specified in as part of the hash).
For more information see https://en.wikipedia.org/wiki/Crypt_(C)
I understand that password_hash returns the salt and hashing algorithm as part of the result, and so I get why password_verify works and will be backwards compatible with hashes generated by password_hash.
However, I have a database of older hashes from a PHP 5.4 environment, which used crypt and a randomly generated salt. To my surprise, password_verify returns expected values of true and false for these too. I see crypt also prepends info about the salt and algorithm to the result string, but password_get_info doesn't seem to parse it.
php > echo crypt('test1','salt');
saTBKtwSCLJ0A
php > echo crypt('test1','sa_anything');
saTBKtwSCLJ0A
php > echo crypt('test1','newsalt');
ne0fA1VwB4hx2
php > echo crypt('test3','$6$salt');
$6$salt$NGtBMjsb3SEYv95mjN8yKuZMkYSjFJQDt8yu8JMnXJLv/NWugOVDTnqPeBqp94mf6T20sHoY.wSNWwtTSPvqM0
php > var_dump(password_get_info('$6$salt$NGtBMjsb3SEYv95mjN8yKuZMkYSjFJQDt8yu8JMnXJLv/NWugOVDTnqPeBqp94mf6T20sHoY.wSNWwtTSPvqM0'));
array(3) {
["algo"]=>
int(0)
["algoName"]=>
string(7) "unknown"
["options"]=>
array(0) {
}
}
One of the user notes in verify_password's documentation states
This function can be used to verify hashes created with other functions like crypt().
but doesn't say why.
I'm wondering what's going on in the background, and whether password_verify is guaranteed to work on crypt-generated passwords or if I should still be checking for them.
The "hash" actually contains the hashing mechanism and the salt at the beginning of the string.
In your example, $6$ stands for SHA-512, you can try man crypt for a few more IDs.
It's easy for the function to get the correct algorithm and check the actual password against its hash.
I've used the following instructions to install a mail server:
http://www.geoffstratton.com/ubuntu-mail-server-postfix-dovecot-and-mysql
Now I'm trying to program a login form in PHP but don't know how to compare the entered password with the saved password.
This is the mysql-code for the password encryption:
ENCRYPT('PASSWORD', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16)))
I don't understand how it works because with every call of this function a completely new string is being generated.
This is what I have so far:
crypt($_POST[‘password’], '$6$'.substr(sha1(rand()), 0, 16))
But as I said every time I get a new string.
Use the PHP functions password_hash and password_verify.
These functions salt and iterate to provide secure protection.
See PHP Manual password_hash and password-verify.
string password_hash ( string $password , integer $algo [, array $options ] )
Returns the hashed password, or FALSE on failure.
boolean password_verify ( string $password , string $hash )
Returns TRUE if the password and hash match, or FALSE otherwise.
Example code:
$hash = password_hash("rasmuslerdorf", PASSWORD_DEFAULT)
if (password_verify('rasmuslerdorf', $hash)) {
echo 'Password is valid!';
} else {
echo 'Invalid password.';
}
In your case you grab the password hash for that username from the database, and keep it in a variable called $hash. Then you use password_verify() like this:
password_verify($_POST["password"], $hash)
Consider the case where a password is simply stored as its hash. Anyone reading the data would have a hard job working out what the password actually is, however its not impossible, indeed there are online database containg vast numbers of indexed hashes and the corresponding cleartext - its possible to simply lookup the hash for commonly chosen passwords. Further, consider the case if two users had the same hash - that also means anyone reading the data would know they had the same password.
Both these problems are addressed on secure system by adding a random string, known as a salt to the cleartext. This salt is only generated once and is then stored alongside the password.
So the data you have stored is $6$[salt]$[hash of (salt + password)]
To verify the password you recreate the hash using the stored salt and the password presented and compare that with stored hash. The crypt function ignores any data after the salt, so you simply do this:
if ($stored === crypt($_REQUEST['password'], $stored)) {
// Password is valid
The code you are using has very low entropy in its salt derivation - this is probably adequate for most purposes but not in highly secure contexts.