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
If i use crypt() to hash a password:
$password = "my_password_12345";
$salt = base64_encode(openssl_random_pseudo_bytes(64, $cstrong));
$crypt = crypt($password, $salt);
I get something like this
echo $crypt; //AG6hHvhjwnqpc
So, when I check for the hash I do this and all work fine
echo crypt($password, $crypt); //AG6hHvhjwnqpc
But why the following happens? I do the same check as above but with a password similar to the previous one and I get the same hash.
$password = "my_password_12345_not!";
echo crypt($password, $crypt); //AG6hHvhjwnqpc
I would expect a different hash, but instead I'm getting the same
In PHP, the crypt function use only the 8 first characters :
Extract from the documentation:
The standard DES-based crypt() returns the salt as the first two characters of the output. It also only uses the first eight characters of str, so longer strings that start with the same eight characters will generate the same result (when the same salt is used).
I am having a strange behavior with the crypt() . Here is my code in Zend:
$correct_password_hash = $this->getHelper('User')->generateHash('bd468cffe6b179d8e5ef30bd993d37e5','572906092501a20f4222a54.54479708');
$edited_password_hash = $this->getHelper('User')->generateHash('bd468cffe6b179d8e5ef30bd993d37e','572906092501a20f4222a54.54479708');
echo "Correct Password Hash - ".$correct_password_hash."<br/>";
echo "Edited Password Hash - ".$edited_password_hash;
I am passing a md5 generated string to the helper function generateHash as first parameter and a salt as second parameter. I store the generated hash in $correct_password_hash variable.
Now in in the second call to the same helper function , i have just removed the letter 5 at the end of the first parameter. the second parameter is the same. But still its generating the same hash as first one.
Here is the output:
Correct Password Hash - 57CO1Lzyk81kk Edited Password Hash - 57CO1Lzyk81kk
The helper generateHash is as follows:
public function generateHash($md5, $salt)
{
return crypt($md5, $salt);
}
Is this how crypt() supposed to work?
Thanks.
crypt() is defaulting to standard DES-based algorithm. Which in turn uses only 8 first characters from the password and 2 first characters from the salt.
See crypt() documentation for more details about how to modify the behaviour of crypt():
http://php.net/crypt
If you are doing password hashing, go with bcrypt.
There is an application written in PHP which I am converting to Ruby. When encrypting passwords the PHP app uses the following code:
if($method == 2 && CRYPT_BLOWFISH) return crypt($pass, '$2a$07$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxx$');
I'm assuming this is using a Blowfish implementation. The x's here are all a-zA-Z0-9 characters.
The Blowfish implementation in Ruby uses the following syntax (taken from http://crypt.rubyforge.org/blowfish.html):
blowfish = Crypt::Blowfish.new("A key up to 56 bytes long")
plainBlock = "ABCD1234"
encryptedBlock = blowfish.encrypt_block(plainBlock)
I don't have a 56 or fewer byte long string and it's not clear what that should be from the PHP version. So how can I write a Ruby function which will encrypt passwords to give the same result as the PHP one?
The PHP code is hashing $pass with the salt $2a$07$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxx$ if CRYPT_BLOWFISH is set (CRYPT_BLOWFISH == 1). The salt has to follow the format indicated in the PHP documentation ("$2a$", a two digit cost parameter, "$", and 22 digits from the alphabet "./0-9A-Za-z").
I'm not sure if you can do it with the library you are referring, but you can use bcrypt-ruby instead.
For your code it would be something like this, I'm using the same data from the PHP example ( http://php.net/manual/en/function.crypt.php ), I only take the 29 first characters of the salt because beyond that PHP ignores it:
require 'bcrypt-ruby'
pass = "rasmuslerdorf" # Here you should put the $pass from your PHP code
salt = '$2a$07$usesomesillystringfors' # Notice no $ at the end. Here goes your salt
hashed_password = BCrypt::Engine.hash_secret(pass,salt) # => "$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi"
This gives you the same output as on the PHP example. If your salt is too long take the first 29 characters ($2a$07$ plus the next 22 extra characters).
I tested the behavior of PHP, if the salt is too long (beyond 29 characters in total) the rest is ignored, if the salt is too short it will return 0. E.g in PHP:
<?php
crypt('rasmuslerdorf', '$2a$07$usesomesillystringforsalt$')
// returns $2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi
crypt('rasmuslerdorf', '$2a$07$usesomesillystringfors')
// returns $2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi
crypt('rasmuslerdorf', '$2a$07$usesomesilly')
// returns 0 because the salt is not long enough
?>
I need to get the basics of this function. The php.net documentation states, for the blowfish algorithm, that:
Blowfish hashing with a salt as follows: "$2a$", a two digit cost parameter, "$", and 22 base 64 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
So this, by definition, should not work:
echo crypt('rasmuslerdorf', '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringforsalt$');
However, it spits out:
$2a$07$usesomadasdsadsadsadaeMTUHlZEItvtV00u0.kb7qhDlC0Kou9e
Where it seems that crypt() has cut the salt itself to a length of 22. Could somebody please explain this?
Another aspect of this function I can't get my head around is when they use crypt() to compare passwords. http://php.net/manual/en/function.crypt.php (look at ex. #1). Does this mean that if I use the same salt for all encrypting all my passwords, I have to crypt it first? ie:
$salt = "usesomadasdsadsadsadae";
$salt_crypt = crypt($salt);
if (crypt($user_input, $salt) == $password) {
// FAIL WONT WORK
}
if (crypt($user_input, $salt_crypt) == $password) {
// I HAVE TO DO THIS?
}
Thanks for your time
Following code example may answer your questions.
To generate hashed password using Blowfish, you first need to generate a salt, which starts with $2a$ followed by iteration count and 22 characters of Base64 string.
$salt = '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringfors';
$digest = crypt('rasmuslerdorf', $salt);
Store the whole $digest in database, it has both the salt and digest.
When comparing password, just do this,
if (crypt($user_input, $digest) == $digest)
You are reusing the digest as salt. crypt knows how long is the salt from the algorithm identifier.
New salt for every password
$password = 'p#ssw0rd';
$salt = uniqid('', true);
$algo = '6'; // CRYPT_SHA512
$rounds = '5042';
$cryptSalt = '$'.$algo.'$rounds='.$rounds.'$'.$salt;
$hashedPassword = crypt($password, $cryptSalt);
// Store complete $hashedPassword in DB
echo "<hr>$password<hr>$algo<hr>$rounds<hr>$cryptSalt<hr>$hashedPassword";
Authentication
if (crypt($passwordFromPost, $hashedPasswordInDb) == $hashedPasswordInDb) {
// Authenticated
Quoting from the manual
CRYPT_BLOWFISH - Blowfish hashing with
a salt as follows: "$2a$", a two digit
cost parameter, "$", and 22 base 64
digits from the alphabet
Note: 22 base 64 digits
BCrypt uses 128 bits for salt, so 22 bytes Base64, with only two bits of the last byte being used.
The hash is computed using the salt and the password. When you pass the crypted password, the algorithm reads the strength, the salt (ignoring everything beyond it), and the password you gave, and computes the hash, appending it. If you have PostgreSQL and pg_crypto handy, SELECT gen_salt('bf'); will show you what of $salt is being read.
Here's a code sample for salt generation, from my .NET implementation's test-vector-gen.php, alternatively:
$salt = sprintf('$2a$%02d$%s', [strength goes here],
strtr(str_replace(
'=', '', base64_encode(openssl_random_pseudo_bytes(16))
),
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
'./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'));
There is no reason to use the same salt for all of your passwords. The salt is part of the output anyway so you gain nothing in convenience... though I grant PHP ought to have a built-in gen_salt function.
First question:
So this, by definition, should not work:
echo crypt('rasmuslerdorf', '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringforsalt$');
Where it seems that crypt() has cut
the salt itself to a length of 22.
Could somebody please explain this?
There isn't a problem with having too many characters... the phrase Using characters outside of this range in the salt will cause crypt() to return a zero-length string referse to outside the range of base 64 not the range of 22 characters. Try putting an illegal character in the salt string, and you should find that you get an empty output (or if you put < 22 characters in, resulting in illegal empty bytes).
Second question:
You pass in the encrypted stored password as salt because the salt string always appears (by design) in the encrypted string, and this way you ensure that you have the same salt for both encryption of stored and user-entered password.
This question is in relation to my response to ZZ Coder's answer. Basically my question is regarding storing the crypt() result in the database. Am I supposed to store the entire output in the database, so that my database looks like this:
--------------------------------------------------------------------------------
| ID | Username | Password |
--------------------------------------------------------------------------------
| 32 | testuser | $2a$07$usesomadasdsadsadsadaeMTUHlZEItvtV00u0.kb7qhDlC0Kou9e |
--------------------------------------------------------------------------------
If yes, then doesn't this kind of defy the purpose of using a salt in the first place? If someone gains access to the db, they can clearly see the salt used for the encryption?
Bonus question: Is it secure to use the same salt for every password?