Using the password "testtest" and the hash "KFtIFW1vulG5nUH3a0Mv" with the following code results in different hashes depending on the PHP version (as shown in the image below):
$salt = "KFtIFW1vulG5nUH3a0Mv";
$password = "testtest";
$key = '$2a$07$';
$key = $key.$salt."$";
echo crypt($password, $key);
Output 1 (PHP v.5.3.0 - 5.4.41, 5.5.21 - 5.5.25, 5.6.5 - 5.6.9): $2a$07$KFtIFW1vulG5nUH3a0Mv$.0imhrNa/laTsN0Ioj5m357/a8AxxF2q
Output 2 (PHP v.5.5.0 - 5.5.20, 5.6.0 - 5.6.4): $2a$07$KFtIFW1vulG5nUH3a0Mv$e0imhrNa/laTsN0Ioj5m357/a8AxxF2q
Here is an example of the problem:
http://3v4l.org/dikci
This is a massive issue if crypt is being used to hash passwords for a login, as depending on PHP version the hash will be different. Anybody understand what this issue is from and how to deal with it?
This is not so much an issue as you may think.
First you should note, that your code is not absolutely correct, BCrypt requires a 22 character salt, but you provided a 20 character salt. This means that the terminating '$' (which is not necessary btw) will be seen as part of the salt, as well as the first letter of your password. The $ is not a valid character for a BCrypt salt though.
Another thing to consider is that not all bits of character 22 are used, this is due to the encoding, ircmaxell gave a good explanation about this. So different salts can result in the same hash, you can see this well in this answer. How different implementations handle this last bits of the character 22 could theoretically change. As long as the crypt function can verify the password with both hashes there is no problem.
The generation of the salt with its pitfalls is one of the reasons, why the functions password_hash() and password_verify() where written, they make the password handling much easier.
Related
I'm creating a script and would ideally like to convert user password into a hash using password_hash function, however I want to make the hashing dynamic to make sure password_hash function actually exists, so i've written the following function to return a hash depending on what function is available.
function generateHash($hashpwd){
if (function_exists('password_hash')) {
return password_hash($hashpwd, PASSWORD_DEFAULT);
} elseif (function_exists('hash')) {
return hash('sha512', $hashpwd);
} else {
return md5($hashpwd);
}
}
Is there anything wrong with this method or will it work? also I am creating the hash using password_hash in the following way:
password_hash($hashpwd, PASSWORD_DEFAULT);
But most places i've seen example syntax in the following format:
password_hash($hashpwd, PASSWORD_DEFAULT)."\n";
Difference being the \n is the \n needed and what is it for?
Every version of PHP that lacks password_hash (below 5.5) is now end-of-lifed and insecure as a result. It is a) not worth accounting for these now dangerously-insecure versions of PHP, and b) not worth the security hit to your users caused by downgrading to MD5 as the hashing algorithm.
But most places i've seen example syntax in the following format:
password_hash($hashpwd, PASSWORD_DEFAULT)."\n";
I've never, ever seen this done. \n is a new line. Saving that into your database alongside the hash will cause problems - it'd only be worth doing if you were outputting the hash somewhere for debugging/viewing.
A single unsalted SHA512 is a bad fallback for password_hash. At the very least you need to use a different algorithm here including a salt. Preferably use crypt with a decent hash algorithm, which is what password_hash wraps anyway.
There's a password_hash backport available for PHP 5.3.7+, depending on your OS even some older versions. You shouldn't realistically need to support anything older than that. You need to draw the line in the sand somewhere. Most new projects require at least 5.6+. If you need to support anything older, use a trusted library like PHPass, which is backwards compatible until PHP 3.
password_hash($hashpwd, PASSWORD_DEFAULT)."\n"; is just for debugging output, the trailing newline is not part of the password hash.
You wrote:
Is there anything wrong with this method or will it work? also I am creating the hash using password_hash in the following way:
password_hash($hashpwd, PASSWORD_DEFAULT);
But most places i've seen example syntax in the following format:
password_hash($hashpwd, PASSWORD_DEFAULT)."\n";
Difference being the \n is the \n needed and what is it for?
There is a difference between both, a big one.
The second one will embed a hidden line break character that will make your password verification fail silently.
As per this user contributed note about the use of password_hash() supplied by Jay Blanchard, a member here on Stack Overflow.
Be care when using the example from the documentation which concatenates a newline character \n to the end of the hash, i.e.:
echo password_hash("rasmuslerdorf", PASSWORD_DEFAULT)."\n";
People are storing the hash with the concatenated newline and consequently password_verify() will fail.
Edit:
September 29th, 2017.
Special note:
I revisited the manual on password_hash() today and noticed that they removed the user contributed note http://php.net/manual/en/function.password-hash.php#121017 (supplied by Jay Blanchard) where the documentation was modified to remove the \n reference, where it now reads as:
echo password_hash("rasmuslerdorf", PASSWORD_DEFAULT);
which removed the ."\n" from it.
This now has a bearing on the question asked and answers given.
I believe this had something to do with a bug report filed yesterday, as per this comment in another answer inside this same question.
I also have to note that this modification (of the manual's syntax) could have adverse effects on previously posted answers stating that the url is no longer valid.
Personally, I feel they should have kept that note in there and/or stated something about its previous usage.
Before we begin: Yes I am aware that I should use PHP's password_hash function when actually storing passwords. This is a question about the internals of PHP's hashing system.
So I was messing around with PHP's crypt function the other day, and I noticed some odd behavior with bcrypt.
$password = "totallyagoodpassword";
$salt = "hereisa22charactersalt";
$parameter = '$2y$10$' . $salt;
echo $parameter . PHP_EOL;
echo crypt($password, $parameter);
According to PHP's manual, this code should hash "totallyagoodpassword" using bcrypt, salting it with "hereisa22charactersalt." The output of this hash should be the scheme ("$2y$10$"), followed by the 22 characters of the salt, then followed by 31 characters of hash. Therefore, I should expect "$2y$10$hereisa22charactersalt" and then 31 characters of random base64 characters.
So I run the code:
$2y$10$hereisa22charactersalt
$2y$10$hereisa22charactersalev7uylkfHc.RuyCP9EG4my7WwDMKGRvG
And I can't help but notice how the salt I passed into crypt and the salt that came out aren't the same; specifically, the very last character magically became an "e." After running this with different salts, I still get this same quirk where the last and only last character of the output hash is different.
I'm not a developer for PHP, so I'm sure there is some logic behind this behaviour. But I'm curious.
The docs do not state that the output will include the entire 22 bytes of salt. Also the example on the crypt documentation shows a final "$" on the salt.
crypt('rasmuslerdorf', '$2a$07$usesomesillystringforsalt$')
Producing:
$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi
I have a system running on PHP version 5.2.10 Unfortunately the original programmer misunderstood how crypt() was implemented.
$crypt = crypt(trim($cuPassword), CRYPT_BLOWFISH);
// The programmer thought this is how you configure a blowfish cipher
nb CRYPT_BLOWFISH has a value of zero on this machine.
This works in as much as it produces a random looking password hash eg 0$oZ534I2VvSw
Today, I migrated the software to PHP 5.4.9 and discovered that $crypt becomes *0 , ie an error due to the invalid salt.
My problem is that I have a table of login passwords that I can no longer validate. My question: Is there going to be a way I can recreate the original cipher that ran under version 5.2? What hash was implemented when you passed "0" as a salt?
Your description doesn't really add up. In PHP 5.4.9, I tested this:
var_dump(crypt('hello', 0));
Output:
0$ny0efnQXFkE
Now in PHP 5.5, you'll get *0 when calling crypt('hello', 0). But that's okay! Because this is still true in PHP 5.5: this crypt('hello', '0$ny0efnQXFkE') == '0$ny0efnQXFkE'.
All you need to do is change how you generate your hash for new passwords. Validating existing passwords will continue to work.
For good measure, after people successfully log in, check if their hash begins with 0$. If it does, rehash the password (since they entered it, you know what it is) with the updated, proper crypt call.
I tried all valid two digit combinations (CRYPT_STD_DES) and I found that "0q" is equivalent (nearly).
PHP 5.2.10
crypt(trim($cuPassword), CRYPT_BLOWFISH);
Result = 0$txv6CWBxJ9Y
PHP 5.4.9
crypt(trim($cuPassword), '0q');
Result = 0qtxv6CWBxJ9Y
All I need to do is adjust the second character and I can match passwords again.
No, there's no way you can recreate the original cipher. Otherwise even a boy scout would be able to break blowfish.
Your best chance is to generate a random password for your users and hash it once again, then force them to change the password as soon as they login.
"$" is not a valid salt value according to crypt(3) so you need to find a crypt implementation that's equally broken as the one PHP/libc used to have :)
If verifying old passwords is enough, use Matthews answer, else try e.g. openssl which currently still seems to accept "0$" as salt:
$ echo -n "secret" | openssl passwd -crypt -salt '0$' -stdin
0$z.PXBBy6uY.
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.
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.