I found the following example on php.net to secure password before storing it. However, I don't quite understand line 3. Could someone explain this a little bit? Thanks!
1 <?php
2 $password = crypt('mypassword'); // let the salt be automatically generated
3 if (crypt($user_input, $password) == $password) {
4 echo "Password verified!";
5 }
6 ?>
crypt is a one-way function and returns a string that already contains the salt,
When comparing the user input with the crypt result, the function automatically extracts the salt from the string.
To be more clear :
crypt() outputs a string that contains both the salt and the result of the hash. When you pass it that string as a salt, it knows to extract only the salt part and ignore the hash part. And it still returns a string containing both the salt and the hash. So these strings can be compared
You can clearly understand by:
when user signups for the first time , the process is:
$password = crypt($user_input); // let the salt be automatically generated
if (crypt($user_input, $password) == $password) {
echo "Password verified!";
}
when user tries to login , the process will be :
if(crypt($user_passsword_currentlyin_db, $user_inputted_password) == $user_inputted_password) {
echo "Password verified!";
}
Hope you get the thing :)
EDIT:
The output of crypt consists of:
When you pass this output as "salt" back to crypt, it will extract the right algorithm and salt, and use these for the operation. If there is only an algorithm mentioned, it uses this one and generate random salt. Otherwise it will choose a default algorithm and generate random salt. The hash part in the passed salt parameter is ignored.
So you can simply compare your stored_hash with crypt(password, stored_hash) - if it is equal, it quite likely was the right password.
Here is an pseudocode explanation (in PHP-like syntax) how crypt works:
function crypt($password, $salt)
{
if (substr($salt,0 1) == "_") {
$count = substr($salt, 1, 4);
$real_salt = substr($salt, 5, 4);
return "_" . $count . $real_salt . crypt_ext_des($password, $count, $salt);
}
if(substr($salt, 0, 3) == "$1$") {
list($ignored, $real_salt, $ignored) = explode("$", $salt);
return "$1$" . $real_salt . "$" . crypt_md5($password, $real_salt);
}
if(substr($salt, 0, 4) == "$2a$") {
$cost = substr($salt, 4, 2);
$real_salt = substr($salt, 7, 22);
return "$2a$" . $cost . "$" . $real_salt . crypt_brypt($password, $real_salt, $cost);
}
// ... SHA256 and SHA512 analogons
// no match => STD_DES
$real_salt = substr($salt, 0, 2);
return $real_salt . crypt_std_des($password, $real_salt);
}
It is considered good security practice not to store plain text passwords on the server side in case the password file is compromised and user's passwords revealed.
Instead, when the user registers, you should use a one-way hash algorithm on the password before storing it in the database. This way, it is impossible to reproduce the user's password if the file is compromised.
When the user logs in, it is not possible to compare the plaintext password they just entered with that in the database because the password in the database has been hashed.
However, because the same input to a hash function will always produce the same output, line #3 will hash the plaintext input the user just entered and compare it with the hash stored in the database. That way you are comparing hashes with hashes and can establish if the user is authenticated.
A couple notes on security: you should always fail-safe, so your code should read:
if(crypt($user_input, $password) == $password) {
echo "Password verified!";
} else { die("Wrong username/password"); }
Also note that a compromised password file can be reconstructed using a rainbow table.
$user_input is nothing but your password which is stored in the DATABASE
if(crypt($stored_passsword_in_db, $current_password) == $current_password) {
echo "Password verified!";
}
Hope this helps you....
The second parameter is the key. That accepts either nothing, a salt, or a hash that crypt made earlier, which has the salt to make the hash, embedded in it. In the first case, a salt is made up. In the latter case, the salt is used and not the entire hash. So therefore this holds (make sure you understand that, because that's the key to understanding line 3):
crypt($input, $salt) == crypt($input, crypt($input, $salt))
What you have in your database as the user's hashed password, is the left term in that equation. So line 3 can be rewritten as:
if (crypt($user_input, crypt('mypassword')) == crypt('mypassword'))
And the salt was automatically generated in line 2, let's assume it equals 'autosalt'. Line 3 can now be rewritten to this:
if (crypt($user_input, crypt('mypassword', 'autosalt')) == crypt('mypassword', 'autosalt')
And there you go: this is only true if $user_input == 'mypassword'.
Related
I am currently trying to code a login/registration system for a school project.
The password upon registration is encrypted using crypt() and a randomly generated salt and stored in the database.
When logging in, the salt and hash are retrieved from the database together and split using the explode function (I have stored them in salt.hash format, so using . to delimit).
I am then trying to use the user input password along with the salt retrieved from the database to generate a hash and compare it to the original taken from the database. This is where my problem lies.
function fetch_hash($username, $dbc)
{
$fetch_hash = $dbc->prepare("SELECT * FROM users WHERE username = :username");
$fetch_hash->bindValue(":username", $username);
$fetch_hash->execute();
$result = $fetch_hash->fetchAll();
$hash = $result[0][password];
return $hash;
}
$correct_hash = fetch_hash($username, $dbc);
list($salt, $hash) = explode('.', $correct_hash);
$password = "password";
echo "Generated hash : " . crypt($password, $salt); // Returns *0
echo "Generated hash 2: " . crypt($password, "$2y$12$p7MTIQRBzetIWkH5zeqr5"); // Returns the correct hash which matches the on stored in the database
When passing in $salt as a parameter for crypt(), it returns *0 despite having the value of "$2y$12$p7MTIQRBzetIWkH5zeqr5" and when I enter it directly, it will return the correct hash to match the one stored in the database (i.e the login is correct). I really don't understand why it would not work as they are the exact same value.
Thanks in advance for any help :)
With crypt() you don't have to store hash and salt separately or delimit it in any way. crypt is totally able to work with its output:
// in register/password update/etc
$hash = crypt($password, $randomSalt);
// store to database
// in login routine
// retrieve hash as it was stored, no additional processing
if ($hash == crypt($password, $hash)) {
// password matches, proceed with user authentication, sessions etc.
} else {
// password mismatch
}
However, there's a relatively new API specifically intended to work with passwords: password_* functions. It's more intuitive to use and has additional security measures crypt() does not (like resistance to time-based attacks). If your php installation is old (older than 5.5), you can use this PHP polyfill and still use the same API.
Try this.
$correct_hash = '$2y$12$p7MTIQRBzetIWkH5zeqr5.$2mV0NZp92R3g';
list($salt, $hash) = explode('.', $correct_hash);
$password = 'password';
echo "Generated hash : " . crypt($password, $salt . '$'); // Returns *0
echo "Generated hash 2: " . crypt($password, '$2y$12$p7MTIQRBzetIWkH5zeqr5' . '$');
I realize this is not as secure as it could be, but I want to do it this way.
I have this code which generates the password from a user's entry ($password)...
$salt = mcrypt_create_iv(22, MCRYPT_DEV_URANDOM);
$new_password = md5($salt . $password);
$new_password = $salt . $new_password;
This is how I'm trying to check against the saved password:
$split_salt = substr($saved_password, 0, 22);
$incomplete_password = md5($split_salt . $current_password);
$hashed_password = $split_salt . $incomplete_password;
if ($saved_password != $hashed_password) {
$error = "error";
} else {
//Validated
}
As far as I can tell this should work. However, I'm getting the error instead of the validation. Does this have something to do with MCRYPT not producing exactly 22 characters?
I know this is not what you want to hear, but your scheme is so utterly unsafe and a good solution is so simple to implement, that you should reconsider:
// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($password, PASSWORD_DEFAULT);
// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($password, $existingHashFromDb);
Your actual problem is the salt, mcrypt_create_iv() will return a binary string and it can very well contain \0 characters. So it is pure luck if your approach works or not.
This is stored in a database. How do I send this demo password to my user? I cant convert this ($2y$10$Zjk5YzQ4ZTlhMzNlNTUzMO3Wnm1FqXmAb6/4DmyptNGoEdWGLwls.) to normal text.
Is there any solution? I'm also unable to send random password.
demo = $2y$10$Zjk5YzQ4ZTlhMzNlNTUzMO3Wnm1FqXmAb6/4DmyptNGoEdWGLwls.
Here are some functions I used for password checking and generation:
function password_encrypt($password) {
$hash_format = "$2y$10$"; // Tells PHP to use Blowfish with a "cost" of 10
$salt_length = 22; // Blowfish salts should be 22-characters or more
$salt = generate_salt($salt_length);
$format_and_salt = $hash_format . $salt;
$hash = crypt($password, $format_and_salt);
return $hash;
}
function generate_salt($length) {
// Not 100% unique, not 100% random, but good enough for a salt
// MD5 returns 32 characters
$unique_random_string = md5(uniqid(mt_rand(), true));
// Valid characters for a salt are [a-zA-Z0-9./]
$base64_string = base64_encode($unique_random_string);
// But not '+' which is valid in base64 encoding
$modified_base64_string = str_replace('+', '.', $base64_string);
// Truncate string to the correct length
$salt = substr($modified_base64_string, 0, $length);
return $salt;
}
function password_check($password, $existing_hash) {
// existing hash contains format and salt at start
$hash = crypt($password, $existing_hash);
if ($hash === $existing_hash) {
return true;
} else {
return false;
}
}
It is not a good scheme for password recovery, to generate a new password and send it to the user. Instead send the user a link with a random independend code, and store this code in your database (hashed). When the user clicks the link, then let him enter a new password. There is no need to know the old password then, the user is authenticated when he can read the email (his email address).
A forgotten password form, can be posted by everybody: You wouldn't be happy if somebody entered your email, and your password was changed immediately. Retrieving the link, you have the opportunity to either ignore the email, or to confirm that you really want to change the password.
As you correctly pointed out, passwords should be stored as hash, and therefore cannot be retrieved. As soon as the hash of the password is stored in the database, there should be no further need to know the real password. Whenever you need the password later, you will know it, because the user entered it.
To generate the BCrypt hash, i would recommend to use the PHP function password_hash(), there exists also a compatibility pack for earlier PHP versions. Especially the generation of the salt is solved much better there.
P.S.
The hash you described is called BCrypt, it is based on the blowfish cipher which is used for two-way encryption.
I have been trying to learn about hashes and salts stored in a users table withing a mysql DB. I get through with storing them but can't seem to wrap my head around how to validate when the user logs in.
I have looked through and seen about storing the salt and hash seperately and together. The salt that I am producing is random.
Any ideas?
I have posted my code.
<?php
$password = 'passwordwhatever';
//generate the salt
function gen_salt() {
$salt = uniqid(mt_rand(), true) . sha1(uniqid(mt_rand(), true));
$salt = crypt('sha512', $salt);
return $salt;
}
//generate the hash
function gen_hash($salt, $password) {
$hash = $salt . $password;
for($i = 0; $i < 100000; $i++) {
$hash = crypt('sha512', $hash);
}
$hash = $salt . $hash;
return $hash;
}
$password = gen_hash(gen_salt(), $password);
echo $password;
?>
As long as you produce the same salt, it shouldn't matter too much. Store it in your db, in configuration, as long as you can get to it. The effort of reversing a SHA512 hash is so high that unless you're the Pentagon, nobody will bother. The point is that you want to be able to repeat the hash with the same salt so you can be very sure that the input was the same without having to store the sensitive input. If that makes sense.
The salt is going to need to be contained somewhere within the database (or somewhere). Some options are to append the salt to the hash or stored as its own field. I like appending it to the hash.
$password = $salt . '.' . $hash;
Then when the user goes to login, you grab his password, break it into the hash and the salt, and use your password generation function (with the salt from the password instead of a random salt) to determine if it matches the password in the db.
list($salt,$hash) = explode('.', $password);
$check = gen_hash($salt, $input_pass);
if ($check === $password)
// valid
Maybe you should take a look on bcrypt. This might help you. I wrote some tutorials but they are in german. Oh and PHP 5.3 support bcrypt natively.
Here is the example from the PHP manual page for crypt():
<?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!";
}
?>
Why does this work? I take it 'mypassword' is the password I want the actual admin to use. So I crypt that first, and set it equal to $password. Obviously, I must need to store that in the DB. But in the next lines it's being used as both the salt and what I'm comparing to, and I don't understand how crypt($user_input, $password) can possibly be equal to $password, if in this latter case I have ideally the right password as $user_input but salted with $password being compared to $password. It would make more sense to me if the last line were
if (crypt($user_input) == $password) {
echo "Password verified!";
}
What am I not understanding?
crypt is a one-way function and returns a string that already contains the salt. The output is similar to what is stored in /etc/shadow.
Example from php.net:
<?php
echo 'result: ' . crypt('somepassword');
echo 'result: ' . crypt('somepassword');
echo 'result: ' . crypt('somepassword');
?>
result: $1$K2D8DGwq$b05uO37aMwO4rnDlB9Rsi1
result: $1$aPBvu2y.$213YVEs8/5m.jMCXSScly/
result: $1$dW3Xu2p6$nuCtJe2zzlgBMLxN2oZCx/
When comparing the user input with the crypt result, the function automatically extracts the salt from the string.