How to create and store password hashes with Blowfish in PHP
Asked Answered
S

2

27

1) How do you create secure Blowfish hashes of passwords with crypt()?

$hash = crypt('somePassword', '$2a$07$nGYCCmhrzjrgdcxjH$');

1a) What is the significance of "$2a"? Does it just indicate that the Blowfish algorithm should be used?
1b) What is the significance of "$07"? Does a higher value imply a more secure hash?
1c) What is the significance of "$nGYCCmhrzjrgdcxjH$"? Is this the salt that will be used? Should this be randomly generated? Hard-coded?

2) How do you store Blowfish hashes?

echo $hash;
//Output: $2a$07$nGYCCmhrzjrgdcxjH$$$$.xLJMTJxaRa12DnhpAJmKQw.NXXZHgyq

2a) What part of this should be stored in the database?
2b) What data type should be used for the column (MySQL)?

3) How should one verify a login attempt?

Selfdeprecating answered 13/2, 2011 at 10:47 Comment(2)
1.) Read the manual 2. & 3. are questions for themselves.Keynes
Also see Openwall's PHP password hashing framework (PHPass). Its portable and hardened against a number of common attacks on user passwords. The guy who wrote the framework (SolarDesigner) is the same guy who wrote John The Ripper. So he knows a thing or two about attacks on passwords.Ranjiv
D
13

You should store the entire output of crypt, there's not a lot of point in splitting it up, because you need to generate a new salt for each password you're hashing in any case. Using a fixed hidden salt as mentioned by Matt is wrong - the salt should be different for every hash.

For more information see http://www.openwall.com/articles/PHP-Users-Passwords - I recommend using the phpass library because it handles generating a random salt for you, unlike crypt().

Deese answered 13/2, 2011 at 21:47 Comment(3)
chargen.matasano.com/chargen/2007/9/7/… is another excellent resource.Piatt
Rather than PHPass I'm more inclined to used the built in functions provided by PHP. I believe from what I have read, that PHP automatically generates a salt when using the password_hash() function If omitted, a random salt will be generated by password_hash() for each password hashed. This is the intended mode of operation. uk1.php.net/password_hashGlobulin
Yes, since this post was written, PHP added password_hash - that is a better option than phpass now.Deese
K
0

1a) Strength of encryption - requirement in the range of 4..31. See http://php.net/manual/en/function.crypt.php

1b) See 1a

1c) See 1a. 'salt' should not be random, or you would not be able to regenerate the same hash for a given input - see 3.

2a) Strictly speaking, everything except the hash (in case database is compromised). Also, store your salt in a file not accessible beneath the web server's document root and include it. Set it with the strictest permissions possible; ideally read only to web host service (e.g. apache), no write or execute privileges. Less strictly speaking, depends how defensive you wish to be against hackers. Not storing the salt just makes life more difficult; they still have to get the data being input to the algorithm right - but why make it easier?

2b) VARCHAR(32) should be fine for blowfish, if not storing the hash

3) Assuming you've already run the proper injection prevention code, etc.. so please don't just copy the below blindly (and ideally use PDO instead of mysql extension). The below is specific to blowfish, SHA-256 and SHA-512 which all return the salt within the hash. Would need modification for other algorithms...

//store this in another file outside web directory and include it
$salt = '$2a$07$somevalidbutrandomchars$'

...

//combine username + password to give algorithm more chars to work with
$password_hash = crypt($valid_username . $valid_password, $salt)

//Anything less than 13 chars is a failure (see manual)
if (strlen($password_hash) < 13 || $password_hash == $salt)
then die('Invalid blowfish result');

//Drop the salt from beginning of the hash result. 
//Note, irrespective of number of chars provided, algorithm will always 
//use the number defined in constant CRYPT_SALT_LENGTH
$trimmed_password_hash = substring($password_hash, CRYPT_SALT_LENGTH);
mysql_query("INSERT INTO `users` (username,p assword_hash) VALUES '$valid_username', '$trimmed_password_hash'");

...

$dbRes = mysql_query("SELECT password_hash FROM `users` WHERE username = '$user_input_username' LIMIT 1");
//re-apply salt to output of database and re-run algorithm testing for match
if (substring($salt, CRYPT_SALT_LENGTH) . mysql_result($dbRes, 0, 'password_hash') ) ===
        crypt($user_input_username . $user_input_password, $salt) ) {
    //... do stuff for validated user
}
Koumis answered 13/2, 2011 at 12:1 Comment(8)
Note, the above shouldn't be used for a login to protect sensitive data. For that, as a minimum, use a different salt for each user and store the salt value in a separate database (accessible only by a different MySQL user) with a table mapping the username to the salt.Koumis
My CRYPT_SALT_LENGTH is 123, so your substr() line returnes an empty string. What's up with that?Selfdeprecating
I've used $hash = substr($hash, strlen($salt)); or $hash = str_replace($salt, '', $hash);. (They return the same.) Seems to work. It seems the final blowfish hash is 20 letters long, so CHAR(20) might be a proper datatype?Selfdeprecating
The answer above is just plain wrong. In crypt(), you store the entire result of crypt() and feed it back into itself later for validation...Piatt
@TML, is right - this answer is bad advice. The salt should be unique for each hash and is stored as a suffix to the hash by crypt() automatically.Ramage
Having a "fixed" salt is bad idea. The whole purpose of salt is to add "random" bits to an otherwise guessable (e.g. 12345678) password. The chances of attack against stored passwords succeeding with a fixed salt is higher.Makeup
What is the reason to use a random salt for every crypt() if you're going to store that salt in the database, as part of the encrypted password? If your database security is compromised and the intruder has access to that table and can read the "encrypted" password fields, then they have plain access to your "super-secret" and randomly generated salt. Although every-time-random salt may make your crypt() work differently every time, your security will not be increased.Busty
My comment above is not entirely correct. Storing the salt along with the hash will protect you against rainbow-tables look up - because there will be different hashes for the same password, but it will not protect you against brute-force or password-dictionaries. Ex.: if I have access to all the salts and hashes I will be able to check who has a password 'test' or '1234', etc. - isn't that true?Busty

© 2022 - 2024 — McMap. All rights reserved.