Say I wanted to store a password for a user, would this be the right way to do it with PHP 5.5's password_hash() function (or this version for PHP 5.3.7+: https://github.com/ircmaxell/password_compat)?
$options = array("cost" => 10, "salt" => uniqid());
$hash = password_hash($password, PASSWORD_BCRYPT, $options);
Then I would do:
mysql_query("INSERT INTO users(username,password, salt) VALUES($username, $hash, " . $options['salt']);
To insert into database.
Then to verify:
$row = mysql_fetch_assoc(mysql_query("SELECT salt FROM users WHERE id=$userid"));
$salt = $row["salt"];
$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10, "salt" => $salt));
if (password_verify($password, $hash) {
// Verified
}
Ignoring the issues with your database statements for now, I'll answer the question regarding password_hash.
In short, no, that is not how you do it. You do not want to store the salt alone, you should be storing both the hash and salt, and then using both to verify the password. password_hash returns a string containing both.
The password_hash function returns a string that contains both the hash and the salt. So:
$hashAndSalt = password_hash($password, PASSWORD_BCRYPT);
// Insert $hashAndSalt into database against user
Then to verify:
// Fetch hash+salt from database, place in $hashAndSalt variable
// and then to verify $password:
if (password_verify($password, $hashAndSalt)) {
// Verified
}
Additionally, as the comments suggest, if you're interested in security you may want to look at mysqli (ext/mysql is deprecated in PHP5.5), and also this article on SQL injection: http://php.net/manual/en/security.database.sql-injection.php
Using your own salt is not recommended and, as of PHP 7, its use is deprecated. To understand why, the author of password_hash shared these thoughts (link defunct)
One thing has become abundantly clear to me: the salt option is
dangerous. I've yet to see a single usage of the salt option that has
been even decent. Every usage ranges from bad (passing mt_rand()
output) to dangerous (static strings) to insane (passing the password
as its own salt).
I've come to the conclusion that I don't think we should allow users
to specify the salt.
He even made this comment in SO chat noting how bad passing your own salt can be
Note this from php.net
Warning
The salt option has been deprecated as of PHP 7.0.0. It is now
preferred to simply use the salt that is generated by default.
Conclusion? Forget about salt option.
This would be quite enough password_hash('password', PASSWORD_DEFAULT) *(or _BCRYPT)
You should not enter own salt, leave salt empty, function will generate good random salt.
Insert into database (or file or whatever you use) whole the string returned by the function. it contains:
id of algorithm, cost, salt (22 chars) and hash password.
The entire string is required to use password_verify (). Salt is random and does not harm to fall into the wrong hands (with hashed password). This prevents (or very difficult) to use ready sets generated lists of passwords and hashes - rainbow tables.
You should consider add cost parameter. Default (if omitted) is 10 - if higher then function compute hash longer. Increasing the cost by 1, double time needed to generate a hash (and thus lengthen the time it takes to break password)
$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10));
you should set this parameter based on speed check on your server. It is recommended that the function performed 100ms+ (some prefer to make it 250 ms). Usually cost = 10 or 11 is a good choice (in 2015).
To increase security, you might want to add to passwords a long (50-60 characters is good choice) secret string. before you use password_hash() or password_verify().
$secret_string = 'asCaahC72D2bywdu##$##$234';
$password = trim($_POST['user_password']) . $secret_string;
// here use password_* function
Caution
Using the PASSWORD_BCRYPT for the algo parameter, will result in the password parameter being truncated to a maximum length of 72 characters.
If $password will be longer than 72 chars and you change or add 73 or 90 characters hash will not change. Optional, sticking $secret_string should be at the end (after the user's password and not before).
Related
im currently trying to understand hashes and salts. As i understand it, it should not be possible to verify a password, if i only have the password and the generated hash(that was generated with a random salt ofc).
So how can the password_verify function in PHP verify my password, if i dont give it the salt? Is there a hidden variable in the background, that stores it for the php hashing functions?
And if that is the case, how can
doveadm pw -t '{SHA512-CRYPT}$6$myhash...' -p "qwertz"
verify it too, even if i run it on a complety different computer? Thats a tool, that comes with Dovecot(a MDA).
Here is my PHP code, that creates a random salt with 64 chars, combines it with a password, creates a hash and verifies the hash via password_verify().
I just started working on the whole hash/salt/pepper thing today, so there could be a huge flaw in my whole train of thought.
<?php
$password = "qwertz";
$salt = createSalt(64);
$hash = crypt($password, "$6$$salt");
if (password_verify($password, $hash)) {
echo 'Password is valid!';
} else {
echo 'Invalid password.';
}
function createSalt($length){
$chars = "IrhsYyLofUKj4caz0FDBCe2W9NRunTgQvp7qOXmS5GM3EJV6i8tAHdkPbxwl1Z";
$salt="";
for($i=0; $i < $length; $i++){
$newchar = substr($chars, rand(0,strlen($chars)-1),1);
$salt .= $newchar;
}
return $salt;
}
?>
The hash contains several pieces of information. This article explains the format used by Unix but I believe PHP password functions use a similar format (if not the same):
The hash field itself is comprised of three different fields. They are
separated by '$' and represent:
Some characters which represents the cryptographic hashing mechanism used to generate the actual hash
A randomly generated salt to safeguard against rainbow table attacks
The hash which results from joining the users password with the stored salt and running it through the hashing mechanism specified in
the first field
It can also include the exact per-algorithm options used to generate the hash, such us the algorithmic cost:
var_dump(password_hash('foo', PASSWORD_BCRYPT, [
'cost' => 8,
]));
string(60) "$2y$08$7Z5bTz7xXnom8QsrbZ7uQetMLxOZ7WjuDkUYRIh73Ffa17GV1Tb7q"
Here $2y$08$ means that Bcrypt with cost 8 was used.
If we use the newer Argon2 available in PHP/7.2 there're even more params:
$argon2i$v=19$m=1024,t=2,p=2$YzJBSzV4TUhkMzc3d3laeg$zqU/1IN0/AogfP4cmSJI1vc8lpXRW9/S0sYY2i2jHT0
Some backgrounds to the answer from #Álvaro González :
PHP manual suggests using "password_hash" instead of "crypt" function through "password_hash" is a "crypt()" wrapper ( Because, it uses a strong hash, generates a strong salt, and applies proper rounds automatically. )
"password_hash()" returns the algorithm, cost, and salt as part of the returned hash. Therefore, all information that's needed to verify the hash is included in it. This allows the "password_verify" function to verify the hash without needing separate storage for the salt or algorithm information. : http://php.net/manual/en/function.password-verify.php
Since, "password_hash" is a wrapper for "crypt", "crypt" also does the same, ie., returns the algorithm, cost, and salt as part of the returned hash. and thus "password_verify" can verify the hash.
Now, please check the answer given by #Álvaro González
Say I wanted to store a password for a user, would this be the right way to do it with PHP 5.5's password_hash() function (or this version for PHP 5.3.7+: https://github.com/ircmaxell/password_compat)?
$options = array("cost" => 10, "salt" => uniqid());
$hash = password_hash($password, PASSWORD_BCRYPT, $options);
Then I would do:
mysql_query("INSERT INTO users(username,password, salt) VALUES($username, $hash, " . $options['salt']);
To insert into database.
Then to verify:
$row = mysql_fetch_assoc(mysql_query("SELECT salt FROM users WHERE id=$userid"));
$salt = $row["salt"];
$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10, "salt" => $salt));
if (password_verify($password, $hash) {
// Verified
}
Ignoring the issues with your database statements for now, I'll answer the question regarding password_hash.
In short, no, that is not how you do it. You do not want to store the salt alone, you should be storing both the hash and salt, and then using both to verify the password. password_hash returns a string containing both.
The password_hash function returns a string that contains both the hash and the salt. So:
$hashAndSalt = password_hash($password, PASSWORD_BCRYPT);
// Insert $hashAndSalt into database against user
Then to verify:
// Fetch hash+salt from database, place in $hashAndSalt variable
// and then to verify $password:
if (password_verify($password, $hashAndSalt)) {
// Verified
}
Additionally, as the comments suggest, if you're interested in security you may want to look at mysqli (ext/mysql is deprecated in PHP5.5), and also this article on SQL injection: http://php.net/manual/en/security.database.sql-injection.php
Using your own salt is not recommended and, as of PHP 7, its use is deprecated. To understand why, the author of password_hash shared these thoughts (link defunct)
One thing has become abundantly clear to me: the salt option is
dangerous. I've yet to see a single usage of the salt option that has
been even decent. Every usage ranges from bad (passing mt_rand()
output) to dangerous (static strings) to insane (passing the password
as its own salt).
I've come to the conclusion that I don't think we should allow users
to specify the salt.
He even made this comment in SO chat noting how bad passing your own salt can be
Note this from php.net
Warning
The salt option has been deprecated as of PHP 7.0.0. It is now
preferred to simply use the salt that is generated by default.
Conclusion? Forget about salt option.
This would be quite enough password_hash('password', PASSWORD_DEFAULT) *(or _BCRYPT)
You should not enter own salt, leave salt empty, function will generate good random salt.
Insert into database (or file or whatever you use) whole the string returned by the function. it contains:
id of algorithm, cost, salt (22 chars) and hash password.
The entire string is required to use password_verify (). Salt is random and does not harm to fall into the wrong hands (with hashed password). This prevents (or very difficult) to use ready sets generated lists of passwords and hashes - rainbow tables.
You should consider add cost parameter. Default (if omitted) is 10 - if higher then function compute hash longer. Increasing the cost by 1, double time needed to generate a hash (and thus lengthen the time it takes to break password)
$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10));
you should set this parameter based on speed check on your server. It is recommended that the function performed 100ms+ (some prefer to make it 250 ms). Usually cost = 10 or 11 is a good choice (in 2015).
To increase security, you might want to add to passwords a long (50-60 characters is good choice) secret string. before you use password_hash() or password_verify().
$secret_string = 'asCaahC72D2bywdu##$##$234';
$password = trim($_POST['user_password']) . $secret_string;
// here use password_* function
Caution
Using the PASSWORD_BCRYPT for the algo parameter, will result in the password parameter being truncated to a maximum length of 72 characters.
If $password will be longer than 72 chars and you change or add 73 or 90 characters hash will not change. Optional, sticking $secret_string should be at the end (after the user's password and not before).
I don't understand the documentation at php.net. It appears they are using the encrypted version of password as the salt when testing against the original encryption.
When I insert crypt with out the optional second parameter (the salt) I get different encrypted versions of the same password. Is this expected behavior?
However if I insert a second parameter of 'd4' then I get the same encrypted passwords for the same password input. Expected behavior.
Prior to insertion on signup:
$pass = crypt('$pass', 'd4'); // after this I insert $pass into the mysql table
Testing on signin:
$pass = crypt($pass, 'd4'); // after this I test $pass against the mysql table
PHP.net documentation:
<?php
$password = crypt('mypassword'); // let the salt be automatically generated
/* You should pass the entire results of crypt() as the salt for comparing a
password, to avoid problems when different hashing algorithms are used. (As
it says above, standard DES-based password hashing uses a 2-character salt,
but MD5-based hashing uses 12.) */
if (crypt($user_input, $password) == $password) {
echo "Password verified!";
}
?>
How does this work?
Since crypt() only uses the first two characters (or whatever CRYPT_SALT_LENGTH is) of the salt argument, passing in the encrypted password (of which the first characters are the salt originally used to encrypt it) does the right thing.
If no salt argument is passed in, a random salt is generated and used.
If your question is... Is it normal that with certain encryption ciphers you are returned with different encrypted strings for the same password/input? The answer is yes. Not to sure what you are refering to about salt. Salt it just salt. In the end it is a deturant and means nothing. Using passwords or encrypted forms of passwords as salt is not recommended, but using some random hash (base64) of a phrase is commonly used. Let me know if this doesn't any, and I'll try again.
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.
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?
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().
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
}