How to Use Password Hashing With Pdo to Make My Code More Secure

How do I use password hashing with PDO to make my code more secure?

Just use a library. Seriously. They exist for a reason.

  • PHP 5.5+: use password_hash()
  • PHP 5.3.7+: use password-compat (a compatibility pack for above)
  • All others: use phpass

Don't do it yourself. If you're creating your own salt, YOU'RE DOING IT WRONG. You should be using a library that handles that for you.

$dbh = new PDO(...);

$username = $_POST["username"];
$email = $_POST["email"];
$password = $_POST["password"];
$hash = password_hash($password, PASSWORD_DEFAULT);

$stmt = $dbh->prepare("insert into users set username=?, email=?, password=?");
$stmt->execute([$username, $email, $hash]);

And on login:

$sql = "SELECT * FROM users WHERE username = ?";
$stmt = $dbh->prepare($sql);
$result = $stmt->execute([$_POST['username']]);
$users = $result->fetchAll();
if (isset($users[0]) {
if (password_verify($_POST['password'], $users[0]->password) {
// valid login
} else {
// invalid password
}
} else {
// invalid username
}

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

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 table2 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, i.e. 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()).

pass in password_hash field with pdo

Its really quite simple once you read the manual or see an example in a tutorial. See comments in the code for details

<?php
include_once("config.php");
session_start();

if(isset($_POST['signup'])){
$name = $_POST['name'];
$email = $_POST['email'];

// at signup you hash the user provided password
$pass = password_hash($_POST['pass'], PASSWORD_DEFAULT);

$insert = $pdo->prepare("INSERT INTO users (name,email,pass)
values(:name,:email,:pass) ");
$insert->bindParam(':name',$name);
$insert->bindParam(':email',$email);
$insert->bindParam(':pass',$pass); // this stores the hashed password
$insert->execute();
}elseif(isset($_POST['signin'])){
$email = $_POST['email'];
$pass = $_POST['pass'];

// as the password on the DB is hashed you cannot use the
// plain text password in the SELECT here as it wont match
$select = $pdo->prepare("SELECT * FROM users WHERE email=:email");

// no idea what this was doing
//$select->setFetchMode();
$select->bindParam(':email',$email);
$select->execute();

$row = $select->fetch(PDO::FETCH_ASSOC);

// verify the plain text password against the
// hashed value from DB in $row['pass']
if( password_verify($pass, $row['pass']) ){
$_SESSION['email'] = $data['email'];
$_SESSION['name'] = $data['name'];
header("location:profile.php");
exit;
} else {
echo "invalid email or pass";
}
}

And as to the length of the column in the database that you need to hold this hashed value, it is documented in the manual

The following algorithms are currently supported:

  • PASSWORD_DEFAULT - 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_BCRYPT - Use the CRYPT_BLOWFISH algorithm to create the hash. This will produce a standard crypt() compatible hash using the "$2y$" identifier. The result will always be a 60 character string, or FALSE on failure.

How to show and hide a password in a secure way using PDO

I found something that I was looking for.

Unfortunately will not be able to use the password hashing method, so I would need to create my encryption and decryption function by using "openssl_encrypt()" and found this article from geeksforgeeks.com and it explains the process.

I will then just modify and encrypt between SQL and PHP to make this work in the way I want it to.

Link: https://www.geeksforgeeks.org/how-to-encrypt-and-decrypt-a-php-string/

Example Code

<?php

// Store a string into the variable which
// need to be Encrypted
$simple_string = "Welcome to GeeksforGeeks\n";

// Display the original string
echo "Original String: " . $simple_string;

// Store the cipher method
$ciphering = "AES-128-CTR";

// Use OpenSSl Encryption method
$iv_length = openssl_cipher_iv_length($ciphering);
$options = 0;

// Non-NULL Initialization Vector for encryption
$encryption_iv = '1234567891011121';

// Store the encryption key
$encryption_key = "GeeksforGeeks";

// Use openssl_encrypt() function to encrypt the data
$encryption = openssl_encrypt($simple_string, $ciphering,
$encryption_key, $options, $encryption_iv);

// Display the encrypted string
echo "Encrypted String: " . $encryption . "\n";

// Non-NULL Initialization Vector for decryption
$decryption_iv = '1234567891011121';

// Store the decryption key
$decryption_key = "GeeksforGeeks";

// Use openssl_decrypt() function to decrypt the data
$decryption=openssl_decrypt ($encryption, $ciphering,
$decryption_key, $options, $decryption_iv);

// Display the decrypted string
echo "Decrypted String: " . $decryption;

?>

Insert password hash using PDO Prepared Statements

If you wanted to hash using MD5, you could do the following with the password before constructing the SQL statement:

$pass1 = md5($pass1);
$sql = "INSERT INTO contractors ( userid, password, name ) VALUES ( '$userid', '$pass1', '$name' )";
$result = $dbh->prepare($sql);
$count = $result->execute();

echo $count."<br>";

The idea is the same even if it is another hash function. Hash the password before constructing the SQL statement.

As Fiarr and VoteyDisciple have noted in the comments below, opt for a SHA hash as it is more secure.

sha1()

Is this using PDO and prepared statements correctly for a secure login?

That looks good so far. I have three suggestions, though:

  1. Choose a longer salt
  2. Don't store salt and password digest separately
  3. If your database connection is not to localhost use a different database connector: PDO doesn't (yet) support SSL connections

EDIT: Also, validate the input a client provides as a "username". But since your code sample is just an excerpt I guess you're doing that.

EDIT #2: When I said, "don't store salt and password separately" I meant incorporating the salt into the stored password hash. Since the hash algorithm of your choice produces quite a long string (64 character) composed of [0-9a-f] you might want to contemplate generating a salt of random length (credits to ircmaxell here) and either concatenate that to the beginning or end of the password hash. So you'll end up storing variable length values (96 - 128 characters) worth of meaninglessness (to outsiders):

$hash = hash('sha256', $pass);
$salt = substr(hash('sha256', mt_rand(0, 1337)), mt_rand(0, 31), 32);
$hash = $salt . hash('sha256', $salt . $hash . $pass);

Simple but safe password hashing

With password_hash() you are on the right track. For PHP versions 5.3.7 - 5.5 you can use the compatibility pack, later when you switch to a newer PHP version, you can simply remove this php file from your project and the code will still run.

// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($password, PASSWORD_BCRYPT);

// 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);

Even for lower PHP versions than 5.3.7 you can use the compatibility pack‌​. You only have to edit line 55 and change the algorithm from sprintf("$2y$%02d$", $cost); to sprintf("$2a$%02d$", $cost);. This is of course not optimal, but it is the best you can do for PHP between 5.3 and 5.3.7.

The problem with other algorithms like SHA* or MD5 is, that they are ways too fast. It is possible to calculate about 3 Giga SHA-1 per second with common hardware, that makes brute-forcing too easy. To test a whole english dictionary you would need only a fraction of a millisecond. That's why one should use a hash algorithm with a cost factor like BCrypt or PBKDF2, they allow to control the necessary time to calculate a single hash.

How to use PHP's password_hash to hash and verify passwords

Using password_hash is the recommended way to store passwords. Don't separate them to DB and files.

Let's say we have the following input:

$password = $_POST['password'];

You first hash the password by doing this:

$hashed_password = password_hash($password, PASSWORD_DEFAULT);

Then see the output:

var_dump($hashed_password);

As you can see it's hashed. (I assume you did those steps).

Now you store this hashed password in your database, ensuring your password column is large enough to hold the hashed value (at least 60 characters or longer). When a user asks to log them in, you check the password input with this hash value in the database, by doing this:

// Query the database for username and password
// ...

if(password_verify($password, $hashed_password)) {
// If the password inputs matched the hashed password in the database
// Do something, you know... log them in.
}

// Else, Redirect them back to the login page.

Official Reference



Related Topics



Leave a reply



Submit