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?
Related
I'm ok really confused on the crypt() PHP function.
How does the following two crypt functions give the same output when the second crypt is clearly using an different 2nd argument? Diff salt means diff hash right?
echo crypt("password", '$2y$09$anexamplestringforsalt$')."\n<br>";
echo crypt("password", crypt("password", '$2y$09$anexamplestringforsalt$'))."\n<br>";
output:
$2y$09$anexamplestringforsale/.K.VdgECUVEd9N4ja3u1WtgPi5BXZq
The reason is because the salt is part of the output of the hash that crypt provides.
$2y$09$anexamplestringforsale/.K.VdgECUVEd9N4ja3u1WtgPi5BXZq
That is broken into several components:
2y - algorithm identifier (bcrypt)
09 - the cost parameter
anexamplestringforsale - the salt
/.K.VdgECUVEd9N4ja3u1WtgPi5BXZq - the hash
This results in the nice property of just being able to use the result hash directly as the salt in the verification call.
$hash = crypt($password, $salt);
if ($hash === crypt($password, $hash)) {
Now you don't need to store the algorithm, cost or salt separately. Just store them in the hash result directly. Simple.
Also, I'd highly suggest you use the simplified password hashing API, which was designed to specifically mitigate these issues: password_hash().
If you use BlowFish algorithm, your code will finally run into this function: BF_crypt (the source code)
The declaration:
static char *BF_crypt(const char *key, const char *setting,
char *output, int size,
BF_word min)
The key is the $str and the setting is the $salt of php function string crypt ( string $str [, string $salt ] ), and the output will be the encrypted return value.
As you can see in the source code:
First, at line 777, memcpy(output, setting, 7 + 22 - 1);, this line copy the first 29 character(from pos 0 to pos 7 + 22 -1) of $salt into the return value, which is $2y$09$anexamplestringforsale
Second, the remains of $salt never got used.
Third, at line 784, BF_encode(&output[7 + 22], data.binary.output, 23);, append the encrypted string on the return value.
So, the $str and the first 29 character of $salt are factors that affects your return value.
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 this code in a PHP page which I am running multiple times. Every time I refresh the page, the salt changes (as it should), but the hash output stays the same.
$iv = mcrypt_create_iv(22);
$ro = rand(6, 9);
$salt = '$2y$'.$ro.'$'.$iv.'$';
echo $salt;
echo '<br />';
$crypt = crypt('test', $salt);
echo $crypt;
Shouldn't the random salt affect the output and make it so that every time I refresh the page the crypt result changes too?
I also have a few general questions on crypt().
Is there any way for you to use a specific hashing algorithm with this function? I would like to use the blowfish algorithm.
Is it the salt length/format that affects which algorithm it chooses?
Lastly, should the salt length for the blowfish algorithm always be 22 characters, or is that just the maximum?
By the way, if anyone is wondering (and if it matters for answering these questions and wasn't obvious), I'm planning to use something similar to store hashed passwords.
Thank you for looking!
The crypt() function on your system does not support the "2y" algorithm. At least Linux GLIBC 2.7 based systems only know DES, $2a$ (blowfish), $5$ (SHA-256) and $6$ (SHA-512). Therefore, the crypt() function assumed DES and only took the first two characters "$2" as salt. That of course produced always the same output.
Try SHA-512 for secure password hashes:
$salt_chars = array_merge(range('A','Z'), range('a','z'), range(0,9));
$salt = '$6$';
for($i=0; $i < 8; $i++) { $salt .= $salt_chars[array_rand($salt_chars)]; }
echo "salt=$salt\n";
$crypt = crypt('test', $salt);
echo "crypt=$crypt\n";
Regarding your second question, you can chose the algorithm by starting the salt with e.g. "$2a$" (instead the $6$ above) for blowfish. Read "man 2 crypt" for details. For some algorithms you can also encode more parameters like "rounds=4000" into the salt.
According the the crypt() manpage, the salt may be up to 16 characters following the $id$.
Longer salts will silently be truncated and produce the same output as for only the first 16 characters.
BTW, even in /etc/shadow, passwords only use 8 characters of salt with the SHA-512 algorithm. As the salt is only to make rainbow table attacks harder, this seems sufficient.
Not sure why but i think i have totally not understood how this works....
I found an example script which had this:
echo crypt('abc123', '$2a$04$saltsaltsaltsaltsaltxx');
And it claims to give the output:
$2a$04$saltsaltsaltsaltsaltxuK2.MS4sJd6ZjnuS0fp2eenjndo.g4hS
But when i did it the same code i get:
$2pGiQ0v1IyNY
As an output... doesn't really explain anything to me so far or why i get a different output to the example i saW?
I'm trying to get the sale + the hashed password and store them in the user table for each user but I'm not following how to:
a) generate a random salt per user
b) get the salt and the hash password from it to store it ?
c) how you then check it on for example a login page
From PHP docs:
Blowfish hashing with a salt as follows: "$2a$", 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.
You don't need to split the salt from the hashed password. You store the entire string ("$2a$04$saltsaltsaltsaltsaltxuK2.MS4sJd6ZjnuS0fp2eenjndo.g4hS"), and when you want to verify if a provided password matches your hash, you do
if (crypt($form_password, $stored_hash) == $stored_hash) {
// password is correct
}
i'm testing several combinations of sha1 and md5:
<?php
$test = 'fail test';
echo nl2br ("Text: $test\n");
echo nl2br ("md5: ".md5($test)."\nsha1: ".sha1($test)."\nsha1(md5): ".sha1(md5($test))."\nmd5(sha1): ".md5(sha1($test)));
?>
Output:
Text: fail test
md5: 748410d0085967c496d54dd8fcbecc96
sha1: d730125e8cb8576459173655148fb6896ef44c09
sha1(md5): faa3ebeecfec45e509e93e6b245a69e2a78785ea
md5(sha1): b48e89b85c350c91eb302c1de96d4249
Which one better, or maybe user something else ? If yes, what then ?
Both of them are cryptographic hash functions that operate 1-way only, the main difference being that MD5 output size is 128 bits whereas SHA-1 is 160 bits. In brief, I don't see they are much different to use despite MD5 is more common these days.
Curiously, I can't really see how md5($text) is different from md5(sha($text)) when they all encrypted to a 32 character-long string, what about md5($text."token") for example?
And, what do you mean by better? Is it more good looking or more security? See bcrypt if you prefer security :) Wikipedia: http://en.wikipedia.org/wiki/Bcrypt
Hashing a hash adds no extra security. (In fact, it might make it worse if the person has a hash-of-hash lookup table.)
The best hash will be the one that is computationally the most expensive to perform without any vulnerabilities. I would hash passwords with at least sha-256.
Always hash your passwords with a salted key. This key should be unique per password. It doesn't need to be stored privately. The purpose of a salted password is that the hacker who gained access to your database cannot simply compare the hash with a known list of hashes that correspond to common passwords. Instead, he must try to brute force the password by trying every possible password.
By using a unique salt per password, you guarantee that each hash in the database is different, even if they use the same password.
To salt a password, simply create a random string of characters and append it to the password. Here's a sample hash with a 48-bit salt and sha-256:
function make_password($password)
{
# random 48-bit salt (8 chars when base64 encoded)
$salt = base64_encode(pack('S3', mt_rand(0,0xffff), mt_rand(0,0xffff), mt_rand(0, 0xffff)));
return $salt.hash('sha256', $salt.$password);
}
function check_password($password, $hash)
{
$salt = substr($hash, 0, 8);
return hash('sha256', $salt.$password) == substr($hash, 8);
}
$password = 'password';
$hash = make_password('password');
echo $hash."\n";
var_dump(check_password('password', $hash));
var_dump(check_password('wrong', $hash));
Every time you run it, the hash will be different. To validate a password, you select the row where the username matches, and then call check_password($password_from_user, $hash_from_db).
Here's a sample output:
AzrD1jZzc693714a43ad5dfd4106c0a620ef23ff9915070711fa170a6670b8164862b496
bool(true)
bool(false)
You can use a larger salt or a stronger hashing algorithm if you prefer. But at minimum, I would use something like the above.
You should salt your passwords, ALWAYS. This doesn't stop brute force through a login form but if someone managed to get the details, it would be much harder to crack (rainbow tables would be useless unless they manage to get your salt too)
Essentially, if you adding onto the original data or mangling in a controlled way, it will make security a little better. No-one can ever reverse a hash but they can find other inputs thats match the hash. Mangling the user input will make it harder to login for the hackers.
for example, if a user's pass is 123456, if you add a salt of "salt" to it so it becomes 123456salt, the MD5 of this would be 207acd61a3c1bd506d7e9a4535359f8a. A hacker could crack this to become 123456salt but when it comes to using that on your login form, your code will add salt again and the login will fail.