PHP Password_Hash(), Password_Verify()

Using password_hash and password_verify

On signup you get the password from the user input and generate its has using password_hash():

$hash = password_hash($_POST['password'], PASSWORD_BCRYPT);

You can provide it a custom salt to use, in a third parameter, but the documentation recommends to not do this:

Caution It is strongly recommended that you do not generate your own salt for this function. It will create a secure salt automatically for you if you do not specify one.

You save this hash in the database. Make sure you put it in a CHAR/VARCHAR field of 60 characters or longer.

When the user wants to log in you check the password they input against the hash previously saved using password_verify():

$auth = password_verify($_POST['password'], $hash);

Of course, you get the correct value of $hash from the database, searching by the provided username.

If $auth is TRUE then the provided password matches its hash computed on the registration and the user is authenticated.

PHP password_hash(), password_verify()

Here is what I use for password_hash and password_verify. Try it out as written, you can then start adding in the rest of your code once successful.

Modify table and column name(s) to suit.

N.B.: This is a basic insertion method. I suggest you use prepared statements instead.

Sidenote: The password column needs to be long enough to accomodate the hash VARCHAR(255). Consult "Footnotes".

INSERT file

<?php
$DB_HOST = 'xxx';
$DB_USER = 'xxx';
$DB_PASS = 'xxx';
$DB_NAME = 'xxx';

$conn = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME);
if($conn->connect_errno > 0) {
die('Connection failed [' . $conn->connect_error . ']');
}

$password = "rasmuslerdorf";
$first_name = "john";
$password = password_hash($password, PASSWORD_DEFAULT);

$sql = "INSERT INTO users (`name`, `password`) VALUES ('" .$first_name ."', '" .$password ."')";

$query = mysqli_query($conn, $sql);
if($query)

{
echo "Success!";
}

else{
// echo "Error";
die('There was an error running the query [' . $conn->error . ']');
}

LOGIN file

<?php
// session_start();

$DB_HOST = 'xxx';
$DB_USER = 'xxx';
$DB_PASS = 'xxx';
$DB_NAME = 'xxx';

$conn = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME);
if($conn->connect_errno > 0) {
die('Connection failed [' . $conn->connect_error . ']');
}

$pwd = "rasmuslerdorf";
$first_name = "john";

//$sql = "SELECT * FROM users WHERE id = 1";

$sql = "SELECT * FROM users WHERE name='$first_name'";
$result = $conn->query($sql);
if ($result->num_rows === 1) {
$row = $result->fetch_array(MYSQLI_ASSOC);
if (password_verify($pwd, $row['password'])) {

//Password matches, so create the session
// $_SESSION['user'] = $row['user_id'];
// header("Location: http://www.example.com/logged_in.php");

echo "Match";

}else{
echo "The username or password do not match";
}

}

mysqli_close($conn);

Footnotes:

The password column should be long enough to hold the hash. 72 long is what the hash produces in character length, yet the manual suggests 255.

Reference:

  • http://php.net/manual/en/function.password-hash.php

"Use the bcrypt algorithm (default as of PHP 5.5.0). Note that this constant is designed to change over time as new and stronger algorithms are added to PHP. For that reason, the length of the result from using this identifier can change over time. Therefore, it is recommended to store the result in a database column that can expand beyond 60 characters (255 characters would be a good choice)."

password_verify and password_hash in php

It can tell because the hash itself has the required information.

Per PHP documentation:

Note that 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 verify function to verify the hash without needing separate storage for the salt or algorithm information.

Reference

For example, a PASSWORD_BCRYPT hash would look like this:

$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
|-----|----------------------|------------------------------|
A1,A2 B C
  • A1: 2a indicates the hash algorithm. For Bcrypt it's either 2a, 2b, or 2y
  • A2: 10 is the cost parameter of the hash. This makes the hash safe against timing attacks, since you can just increase this number to increase the "work" it's needed to compute
  • B: this is the 128-bit salt
  • C: the resulting 184-bit hash

With all those parameters (for now, just looking at a1) it can already tell what algorithm it is.

Problems with password_hash() and password_verify()

This returns a boolean (true/false) value:

password_verify($user['password'], $password)

So this will never be true:

$user['password'] === password_verify($user['password'], $password)

Once you've selected the user from the database based on the provided username, just verify the password:

if (password_verify($user['password'], $password)) {

A couple notes on your terminology, becase it's important...

I want to crypt the user's password

No, you do not want to encrypt the user's password. You want to "hash" it. It's an important distinction. Encrypted things can be returned to their original form. Hashed things can not. Which is a vital part of password security.

compare the dehashed password with the hashed password

There's no "dehashed" anything. What the internals of password_verify does is hash the provided password and compare that result with the already-hashed stored password. At no point can you in any way convert the stored hashed password back to its original form.

PHP can not login with correct password using password_hash()/password_verify()

I've created a simple gist where you can see a version of your code which I managed to run correctly and got the correct results:

  • a user can signup
  • a user can sign in
  • a user can sign out

I find that your code was correct, in broad strokes, but I also believe you must have mixed up some of your variables and field names.

In your question you do a var_dump of $row['pwdUsers'], a field never seen in the insert statement. You also select the field username when fetching data to compare to the one submitted for login, but in the insert statement the variable that you insert into that field is named $mailuid (while a variable $username sits next to it).

Given that when extracted and applied in a controlled environment your code works, I'd wager your error is due to a possible mix-up caused by the previously mentioned confusing nomenclatures, or due to some other logic hidden from us in the snippets you provided.

password_verify() returning false when passwords match

password_verify() works with the function password_hash();

change:

$hash = hash('sha512', $password);

to:

$hash = password_hash($password, PASSWORD_DEFAULT);

PHP password_hash and password_verify incorrect false positive?

As MarkBaker already mentioned in his comment, this is a limitation of the BCrypt algorithm, which truncates passwords to 72 characters. For passwords this is more than enough and not a security problem, but in your case it seems you reach this limit because you want to add a pepper.

Never should a password be truncated because of adding a pepper. A pepper can increase security of weak passwords. Assuming that the pepper is of reasonable size, passwords which reach the limitation are very strong, and for those passwords a pepper is not necessary. So if you put the pepper at the end, you loose a part of the pepper, which is better than loosing a part of the password.

That said, there are much better ways to add a pepper. You get the same benefits from encrypting (twoway) the hash with a server side key. This key is actually similar to the pepper, but you have the advantage that you can exchange the key whenever this seems necessary (a pepper becomes part of the password and cannot be changed until the next login). I tried to explain this at the end of my tutorial about safely storing passwords. If encryption is not an option for you, then at least use a HMAC to combine the pepper with the password.



Related Topics



Leave a reply



Submit