We're in a bit of a bind where we need to use Ruby to auth users against an existing db of users. The user's passwords were all generated using password_compat PHP library. All the hashed passwords start with $2y.
I've been using bcrypt-ruby to try and authenticate the users and I haven't found any success.
#This user's password is "password"
irb(main):041:0> g = BCrypt::Password.new("$2y$10$jD.PlMQwFSYSdu4imy8oCOdqKFq/FDlW./x9cMxoUmcLgdvKCDNd6")
=> "$2y$10$jD.PlMQwFSYSdu4imy8oCOdqKFq/FDlW./x9cMxoUmcLgdvKCDNd6"
irb(main):042:0> g == "password"
=> false
irb(main):044:0> g.version
=> "2y"
irb(main):045:0> g.cost
=> 10
irb(main):046:0> g.salt
=> "$2y$10$jD.PlMQwFSYSdu4imy8oCO"
irb(main):047:0> g.hash
=> -219334950017117414
I'm not very experienced with bcrypt or encryption in general. Can bcrypt-ruby handle $2y? I looked through the source and I don't think it can. Is this the fault of the underlying OS (I'm using OS X)?
Yes, bcrypt-ruby can handle passwords hashed with 2y. You just need to replace the 2y by 2a:
irb(main):002:0> BCrypt::Password.new("$2a$10$jD.PlMQwFSYSdu4imy8oCOdqKFq/FDlW./x9cMxoUmcLgdvKCDNd6") == "password"
=> true
This is necessary as bcrypt-ruby seems to follow Solar Designer’s first suggestion to introduce just 2x for a backward-compatible support for the “sign extension bug”:
[…] I am considering keeping support
for the broken hashes under another prefix - say, "$2x$" (where the "x"
would stand for "sign eXtension bug") instead of the usual "$2a$".
Later he proposed to also introduce the 2y prefix for a better distinction between the three versions:
One idea is to allocate yet another prefix, which will mean the same
thing as 2a, but "certified" as passing a certain specific test suite
(which will include 8-bit chars). So we'll have:
2a - unknown correctness (may be correct, may be buggy)
2x - sign extension bug
2y - definitely correct
Newly set/changed passwords will be getting the new prefix.
PHP supports 2a, 2x, and 2y while bcrypt-ruby supports only 2a, and 2x. But if you know your implementation doesn’t have the “sign extension bug”, you can just replace 2y by 2a, as 2y means the same thing as 2a.
Related
I have a page in my site that allows the user to change there password but as part of the form they have to enter the old password. In the model I have verification to check the old password entered on the form matches the one in the database
public function oldPasswordCheck($attribute, $params)
{
$old_password = $this->attributes['password'];
if (crypt($this->old_password, $old_password) != $old_password)
{
$this->addError('old_password', 'This is not the old password');
}
}
This worked until recently but for some reason now, if you enter the correct old password, it still tells you they didn't match.
Any pointers would be much appreciated.
Some possible answers:
1) Your salt parameter
Php crypt() function uses two parameters: crypt ( string $str [, string $salt ] ). It seems that you are using the password as salt, which is a very bad practice. Is that so or are you just confusing the salt parameter? I would also recommend to always compare passwords with php hash_equals(), because crypt() is vulnerable to timing attacks.
2) changes in your server configuration
What type of hash your php configuration use to crypt/decrypt strings?:
On systems where the crypt() function supports multiple hash types, the following constants are set to 0 or 1 depending on whether the given type is available: CRYPT_STD_DES, CRYPT_EXT_DES, CRYPT_MD5, CRYPT_BLOWFISH, CRYPT_SHA256 and CRYPT_SHA512. If you changed recently your server configuration, some of that could not be available anymore. Check it with phpinfo()
A recommendation
You are working with a framework, and Yii like every framework has its own class to crypt and decrypt strings. Take a look at yii security class. It will make your life easier.
My goal is simply to generate a temporary token that will be used in URLs for user identification, should I use OAuthProvider::generateToken or random_bytes?
From these answers :
Generate a single use token in PHP: random_bytes or openssl_random_pseudo_bytes?
and
best practice to generate random token for forgot password
It seems that random_bytes is a more recently updated option for PHP 7 when compared to openssl_random_pseudo_bytes. Is it the same when compared to OAuthProvider::generateToken?
Examples:
$rb_token = bin2hex(random_bytes($length));
$oa_token = bin2hex((new OAuthProvider())->generateToken($length, TRUE));
// TRUE = strong "/dev/random will be used for entropy"
I would go for random_bytes (if you use PHP < 7, a polyfill with a good reputations exists, random_compat).
Differences in between /dev/urandom and /dev/random are negligible for your case (a more detailed explanation can be found there).
random_bytes use getrandom syscall or /dev/urandom, (source code), while OAuthProvider::generateToken() without strong mode seems to fallback to some unsecure implementation if the generated string is too short (source code, and source code of php_mt_rand). So random_bytes has a edge over usability (no unsafe mode), and on systems using getrandom syscall, as it should be the preferred method (detailled explanation here).
Another way to conclude is to look at some widely-used libraries: FOSOAuthServer bundle, for Symfony applications, uses random_bytes too.
Last but not least, I believe some PHP core function will also receive more scrutinity, and better support, than an extension.
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.
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.