Identifying what PASSWORD_DEFAULT will be in PHP 7.4 - php

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.

Related

How do I use the Argon2 algorithm with password_hash?

So I heard that PHP 7.2 introduced the new Argon2 algorithm. But I'm confused on how I can use it with my existing code. For instance, I have this
$password = password_hash('somepassword', PASSWORD_DEFAULT, ['cost' => 12]);
Does PASSWORD_DEFAULT now use Argon2? What, if anything, do I need to change with password_verify? Is bcrypt considered insecure now?
What is Argon2? Is bcrypt bad now?
Prior to PHP 7.2, the only hashing algorithm password_hash used was bcrypt. As of this writing, bcrypt is still considered a strong hash, especially compared to its predecessors, md5 and sha1 (both of which are insecure because they are fast). Argon2 is simply a costlier algorithm to brute force
Argon2i uses data-independent memory access. It is slower because it makes more passes over the memory to protect from trade off attacks. It is highly recommended for password hashing and password-based key derivation.
Bcrypt is still an acceptable hash for passwords. There's no need to switch if you don't want to (as of the 7.2.0 release). Also, PASSWORD_DEFAULT should only change (per PHP Internals policy) on the next full release (7.3.0 or higher). If you want to ensure you continue with only bcrypt, you can use PASSWORD_BCRYPT instead. This is unnecessary, however, as we'll discuss below.
How do you use Argon2?
First, we'll switch the second argument of password_hash over to one of these to constants
PASSWORD_ARGON2I - PHP 7.2.0+
PASSWORD_ARGON2ID - PHP 7.3.0+ (preferred if available, see notes below)
and then we'll need to change our options. bcrypt uses cost as the parameter for how many times it iterates over the password (higher cost = longer hashing time). There's different cost factors, however
password_hash('somepassword', PASSWORD_ARGON2I, ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3]);
From the manual we see what these options do
memory_cost - Maximum memory (in kibibytes) that may be used to compute the Argon2 hash (default 1024)
time_cost - Maximum amount of time it may take to compute the Argon2 hash (default 2)
threads - Number of threads to use for computing the Argon2 hash (default 2)
Understand, before you go changing these, that a higher cost here will slow down your script. You'll want to run a test on your server to find a setting that works best for you. This is typically by looping over several iterations of a given cost. The PHP manual gives an example of this if you need one.
Also note that, while bcrypt stores 60 characters, Argon2 can require more than that. You should, ideally, make your password field store 255 characters.
What do we change in password_verify?
The answer here is... nothing. Understand that password_verify is smart enough to figure out what algorithm was used and handle it appropriately. As mentioned above, this means that if you are using PASSWORD_DEFAULT, the default can change and not negatively affect you (although you may need to adjust the cost parameters). password_verify simply requires an algorithm it supports. If you switch from bcrypt to Argon2, both will verify the same way, as all the necessary data (salt, hash and cost) are stored for you.
//Works for both bcrypt and Argon2
if(password_verify($user_password, $stored_hash)) {
// password validated
}
If you want to upgrade the hashes from bcrypt, you can do this when a user successfully logs in (and thus supplied you with the un-hashed password). Simply check if your hash starts with $2y$ (the bcrypt marker). If it does, pass the supplied password to password_hash again, but with the Argon2 arguments, and save it to the password field of the logged-in user.
What is Argon2ID?
Introduced in PHP 7.3, Argon2ID makes some improvements over Argon2I as noted in this Crypto.SE question
The best tradeoff attack on 1-pass Argon2id is the combined low-storage attack (for the first half of the memory) and the ranking attack (for the second half), which bring together the factor of about 2.1.
Argon2ID works with the same arguments that Argon2I works with.
Only if you use PHP 7.3:
I've created 2 simple and slim functions to use Argon2ID with PHP:
function argon2idHash($plaintext, $password, $encoding = null) {
$plaintextsecured = hash_hmac("sha256", $plaintext, $password);
return $encoding == "hex" ? bin2hex(password_hash($plaintextsecured, PASSWORD_ARGON2ID)) : ($encoding == "base64" ? base64_encode(password_hash($plaintextsecured, PASSWORD_ARGON2ID)) : password_hash($plaintextsecured, PASSWORD_ARGON2ID));
}
function argon2idHashVerify($plaintext, $password, $hash, $encoding = null) {
$plaintextsecured = hash_hmac("sha256", $plaintext, $password);
return password_verify($plaintextsecured, $encoding == "hex" ? hex2bin($hash) : ($encoding == "base64" ? base64_decode($hash) : $hash)) ? true : false;
}
To get an hashed value use (the last parameter is optional, you can choose hex, base64 or nothing) [return => string]:
$salt = "LALALA";
argon2idHash($clearvalue, $salt, "hex"); // with encoding
argon2idHash($clearvalue, $salt); // without encoding
To verify an hashed value use (parameter $salt must match the salt set at the time of hashing also the same rule applies to encoding if used) [return => bool]:
$salt = "LALALA";
argon2idHashVerify($clearvalue, $salt, $hashtoverify, "hex") ? "match" : "dont match"; // with encoding
argon2idHashVerify($clearvalue, $salt, $hashtoverify) ? "match" : "dont match"; // without encoding
Finally, if you know PHP you can modify these functions to your liking, but for now it's the best way I know to securely store passwords in databases.

How to store a password as BCRYPT in a database as '$2a$15' in PHP

I am wondering on how to store passwords as BCRYPT that starts with $2a$15, because I have already setup my register and it already stores the passwords in BCRYPT.
However, when it stores the passwords as BCRYPT, the output is $2y$15, but I want it as $2a$15. I am new to BCRYPT and I find it quite hard to understand.
Anyway, here is my BCRYPT code:
function encryptedPassword($strPassword) {
$strSHA256 = hash('sha256', $strPassword);
$strBcrypt = password_hash($strSHA256, PASSWORD_BCRYPT, array('cost' => 15, 'salt' => bin2hex(openssl_random_pseudo_bytes(12))));
return $strBcrypt;
}
So, say if my password is Hello123!. The output of that would be:
$2y$15$aa979703a103b0a45b2afO0KS15RjWu5Lc8sa4.xQlsw9QLtyhp/O
and I want it to be:
$2a$15$aa979703a103b0a45b2afO0KS15RjWu5Lc8sa4.xQlsw9QLtyhp/O
I don't know if this is to do with the salt, can someone help me?
From PHP manual:
PASSWORD_BCRYPT - Use the CRYPT_BLOWFISH algorithm to create the hash.
This will produce a standard crypt() compatible hash using the "$2y$"
identifier. The result will always be a 60 character string, or FALSE
on failure.
So using BCRYPT will always produce $2y$. By the way, the begining part of the hash is just an identifier, used for testing the password afterwards with password_verify. Just like the $15$ in your case being the cost of the hashing algorithm...
I suggest you read more on PHP password hashing. There is absolutely no sane reason why you'd want a hash to absolutely start with X or Y...
The PHP folks have put great efforts on giving you secure password hashing functions. You should really try using them without hacking stuff around it. hashing a hash is no safer than just hashing once. The salt option is deprecated in PHP7, I'd suggest you get rid of it, PHP can handle it better than you.

Generating Password Hash In PHP 5.5 And Setting Cost Option

I know PHP 5.5 is in alpha but this class I am making is just being made in advance to utilize it's hashing feature by using function_exists().
I checked out the password_hash documentation. The 3rd argument is for $options which currently supports two options, 'salt' and 'cost'.
It states the following:
cost, which denotes the algorithmic cost that should be used. Examples
of these values can be found on the crypt() page.
When I go to the crypt() page the documentation it gives is:
Blowfish hashing with a salt as follows: "$2a$", "$2x$" or "$2y$", a
two digit cost parameter, "$", and 22 digits from the alphabet
"./0-9A-Za-z". Using characters outside of this range in the salt will
cause crypt() to return a zero-length string. The two digit cost
parameter is the base-2 logarithm of the iteration count for the
underlying Blowfish-based hashing algorithmeter and must be in range
04-31, values outside this range will cause crypt() to fail. Versions
of PHP before 5.3.7 only support "$2a$" as the salt prefix: PHP 5.3.7
introduced the new prefixes to fix a security weakness in the Blowfish
implementation. Please refer to ยป this document for full details of
the security fix, but to summarise, developers targeting only PHP
5.3.7 and later should use "$2y$" in preference to "$2a$".
I can't seem to get my head wrapped around this. It says PHP 5.3.7 and later should use $2y$, but what cost value do I use to get that one and is it the best value to choose? The example they provide uses a value of 7, but according to the above it can go up to 31, what difference does it make to use say 4 opposed to say 31?
The function password_hash() is just a wrapper around the function crypt(), and shall make it easier to use it correctly. It takes care of the generation of a safe random salt, and provides good default values.
The easiest way to use this function would be:
$hash = password_hash($password, PASSWORD_DEFAULT);
That means, the function will hash the password with BCrypt (algorithm 2y), generates a random salt, and uses the default cost (at the moment this is 10). These are good default values, particularly i would not recommend generating the salt of your own, it is easy to make mistakes there.
Should you want to change the cost parameter, you can do it like this:
$hash = password_hash($password, PASSWORD_BCRYPT, ["cost" => 11]);
Increasing the cost parameter by 1, doubles the needed time to calculate the hash value. The cost parameter is the logarithm (base-2) of the iteration count, which means:
$iterations = 2 ^ $cost;
Edit:
I missed the point, that you want to generate your own class. For PHP version 5.3.7 and later, there exists a compatibility pack, from the same author that made the password_hash() function. You can either use this code directly, or look at the well crafted implementation. For PHP versions before 5.3.7 there is no support for crypt with 2y, the unicode aware BCrypt algorithm. You can instead use 2a, which is the best alternative for earlier PHP versions. I did an example with a lot of comments, maybe you want to have a look at it too.
P.S. The expressions "salt" and "cost factor" are used correctly in password_hash(), the crypt() function though, uses the word salt for all crypt parameters together, that's a bit misleading.
Disclaimer: this is with PHP 5.3.10, but it seems not really different from your description.
The cost applies to the cost of computation. When you increase the cost value, it takes longer to hash the password
function blowfish_salt($cost)
{
$chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$salt = sprintf('$2y$%02d$', $cost);
for ($i = 0; $i < 22; ++$i)
$salt .= $chars[rand(0,63)];
return $salt;
}
$password = 'My perfect password';
$cost = $argv[1];
$salt = blowfish_salt($cost);
$hash = crypt($password, $salt);
When I run this on my (old) machine as
php mycrypt.php 10
it returns immediately (~0.2 sec), whereas with
php mycrypt.php 16
it takes about 5.2 seconds.

When using PHP's crypt() function to create a bcrypt hash, do I need a salt?

I understand that PHP's crypt() function works like so:
crypt($password,$salt);
And that to create a bcrypt hash(which is considered very secure) the format is:
crypt("$2y$15$password",$salt);
Where $2y specifies the use of bcrypt, and 15 is the number of rounds, and it should be above 10.
Here's my question. Running:
crypt("$2y$15$password");
in my understanding, generates a nice big random salt and adds it to the hash, and when comparing passwords, this is extracted automatically. This would mean that I wouldn't have to have a salt field in my table, or generate a salt independently. Is this secure and correct?
For example, when I run:
$test = "Hello";
echo crypt("$2y$15$test") . "\n";
I get:
$6$R59d/nemygl0$/Gk6s57K2eFAkH4BWDGYhfdhbYGcqz.GRbD7qGDKOlhE5Lk.kgCoGQo/sDCCf1VDffdh7jtXPn/9rsexwrpFk1
Where the first 6 refers to some algorithm number, the next bit between the two $ is the salt, and the bit after that is the hash. Is this correct?
Also, what is the syntax for comparing this hash to another for verification?
Thanks.
I think crypt as you're using it uses SHA-512.
You probably forgot to pass $test as parameter in your crypt().
According to php docs you should pass the password as first argument (without any prefix) and then pass the salt as second argument with the $2y$15 prefix and a 22 characters salt. E.g.
crypt('rasmuslerdorf', '$2a$07$usesomesillystringforsalt$');
The function crypt() should be used like this:
$hashvalue = crypt($password, $cryptParams);
whereas the salt must be generated by yourself and is part of $cryptParams.
$bcryptAlgo = '$2y';
$cost = '$15';
$salt = '$' . functionThatGenerates22CharRandomSalt();
$cryptParams = $bcryptAlgo . $cost . $salt;
One of the difficulties is to generate a valid, unique and unpredictable salt. The best you can do is to read from the operating systems random source.
PHP 5.5 will have it's own functions password_hash() and password_verify() ready, to simplify this task. I strongly recommend to use this excellent api, there is also a compatibility pack available for earlier PHP versions. If you want to know more about how to use crypt, have a look at this article.
Your cost parameter of 15 is quite high, use it if you can afford this much of time, but today a number of 10 is ok.

(PHP) How to use crypt() with CRYPT_BLOWFISH?

First, I see that to use CRYPT_BLOWFISH, i need to use a 16 char salt starting with $2a$. However, the php.net documentation for crypt() says that some systems don't support CRYPT_BLOWFISH. How often is that the case?
Next, from their example on the docs, I see I use crypt() as follows:
<?php
$password = crypt('mypassword'); // let the salt be automatically generated
/* You should pass the entire results of crypt() as the salt for comparing a
password, to avoid problems when different hashing algorithms are used. (As
it says above, standard DES-based password hashing uses a 2-character salt,
but MD5-based hashing uses 12.) */
if (crypt($user_input, $password) == $password) {
echo "Password verified!";
}
?>
In order to use CRYPT_BLOWFISH, would the only thing I need to modify be the first line to make it like so;
crypt('mypassword', '$2a$07$usesomesillystringforsalt$')
and then the rest of the lines are fine as is?
For PHP before 5.3.0 crypt() used the lib supplied by the OS. If you are using an earlier version, then you'd need to check your OS documentation to see if it is supported (check the value of the CRYPT_BLOWFISH constant) - if not then the algorithm is implemented within the mcrypt() extension for PHP.
The example you've quoted from the docs doesn't seem to make much sense:
$stored_password=fetch_password($user);
if (crypt($_REQUEST['password'],$stored_password)===$stored_password) {
// note that crypt automatically extracts the salt and alogrithm type
// from $stored_password
....
You only need to specify the prefix ($2a$) when creating the password.
HTH
C.

Categories