简体   繁体   中英

PHP PDO how to hash password upon registration and then 'unhash' during login

I am finishing up an application. I have fairly heavy validation going on within the scripting but I want to step it up by hashing the password when it is sent to the database that way it does not go in as plain text. I have tried several methods but found no luck with it as of yet.

Here is the code (Hosted on GitHub's Gist program):

https://gist.github.com/anonymous/e5c4f6ab9443d9ce887266b79bc36a1a

As you can see in the password validation portion of the script, I have it where if the password passes all other validation then it get's hashed. But it doesn't seem to be working when I check my database. That is issue #1. On top of that I couldn't find any methods that would unhash the password and also how to implement an 'unhashing' technique in my login.php file that I included in the link above. I appreciate any help or tips you guys may offer. Thanks so much!

General information on (cryptographic) hashing

Hashing is often confused with encrypting, but they're different and have different use-cases:

Encryption is meant for encoding something such that it can be deciphered again (with the right key), making it a two-way process.

(Cryptographic) hashing , on the other hand, is a one-way process to encode something that is explicitly not meant to be decipherable. Furthermore, a hashing function is deterministic, meaning that given the same message, it will always produce the same digest. 1

So, here is why cryptographic hashing is used to encode passwords:
For hopefully obvious security reasons, you don't want anyone to be able to decipher the hashed password (not some adversary who was able to compromise your database and not even you, the web master, since that would allow you to compromise a user's account somewhere else, if they use the same password over there), but you do want to be able to verify that the password entered by the user matches the password they signed up with.

Since, as mentioned before, a hashing function is deterministic, we can achieve this by comparing the hash produced at an authentication process (such as logging in) and the hash produced at the sign-up process.

"But wait...," I hear you think, "in your comment you told me I would not be able to simply compare them".

If the message given to a bare hashing function is the same, you can and that's the problem with the deterministic aspect of hashing functions, in the context of password hashing: they produce the same output.

This means that if Alice, Bob and Charlie use the same password, they all produce the same digests:

 id | username | pwdhash 
----+----------+---------
 17 | Alice    | 9Wdm...
 48 | Bob      | 9Wdm...
 57 | Charlie  | 9Wdm...

This poses two prominent security risks:

  1. if you know Alice's password, you know the others' as well;
  2. an adversary could, knowing the hashing function you use, theoretically generate, what is called, a rainbow table 2 and compare its hashes with your hashes (if your database was compromised), to infer the actual passwords that match those in the rainbow table.

So, to mitigate these risks it is considered mandatory practice to add, what is called, a salt to the message (the password), before hashing it. A salt is meant to be unique enough to guarantee that two identical passwords produce completely different digests when fed to the same hashing function.

Pseudo example:

// produces same digests:
hash( 'password' ) -> bhZPmushb...
hash( 'password' ) -> bhZPmushb...

// produces unique digests:
        salt       message
hash( 'SeNib6' . 'password' ) -> qDHkfTW9m...
hash( '9pBq7y' . 'password' ) -> XH26YVUnA...

And this, by default, is what password_hash() automatically does for you, under the hood. 3 It then prepends the used salt to the digest, along with information about the actual algorithm that was used and the cost. 4 password_verify() will then use this additional information and the used salt to generate the same digest again, if the provided password matches the earlier hashed password.

It's therefore essential that the complete output of password_hash() is stored so that it can be fed into password_verify() as its second argument.

So, what does all this mean for your case?

Since password_hash() automatically adds a salt and therefore generates two different digests for identical passwords, you can't simply compare password_hash() 's output. This also means you can't (easily; I wouldn't be surprised if there was some way to make it work) compare password hashes at the database level, like you intended, with password = :password ; you will have to compare the hashes in PHP, which means that you will have to fetch the user's record first and then verify the password hashes:

$query = "SELECT * FROM credentials WHERE username = :username";
$statement = $conn->prepare($query);
$statement->execute(array('username' => $_POST["userName"]));

// I believe PDOStatement::rowCount() doesn't work with MySQL for SELECT statements
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
$count = count($rows);
if($count > 1) { // this might be redundant in your setup
  // this is not supposed to happen, usernames should be unique
  // log or alert developer
}
else if($count == 1) {
  $row = $rows[0];
  if(password_verify($_POST["userPass"], $row['password')) {
    // don't store $_POST["userName"] in $_SESSION["username"], but use the database value
    $_SESSION["username"] = $row["username"];
    header("Location: user-homepage.php");
    // it's good practice to exit after a redirect, unless other code still needs to be executed
    exit();
  }
}

$message = '<label>Invalid Credentials</label>';

1) The output of a hashing function is often called the digest or simply the hash.
2) A rainbow table is a database generated by giving the target hashing function a large list of probable passwords, ie generating, by brute force, a long list of digests to compare to a compromised set of digests. This is usually a rather time-intensive operation.
3) I'm not actually sure whether password_hash() prepends or appends the salt to the password to generate the digest.
4) See the documentation for more information about the format of the output (explained in the FAQ about password hashing) algorithms (explained in password_hash() ) and cost (explained in crypt() ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM