Secure passwords with PHP.

This is a short tutorial on how to securely store user passwords with PHP. I will do my best to keep it simple and to-the-point; so that beginner developers can properly understand it.

Do NOT store plaintext passwords.

Passwords should never be stored in plain text. i.e. If a user enters “test123” as their password, then you should NEVER store “test123” in the database. Instead, you will need to use salted password hashing.

Do NOT attempt to create your own password hashing algorithm.

In the past, I’ve come across custom-built functions that attempt to “encrypt” passwords using base64_encode. I’ve also come across functions that attempt to jumble the user’s password around.

For the love of everything that is good in this world, do NOT do this. You are NOT a security expert. Just accept the fact that when it comes to the ins and outs of password security, you know very little. Your custom-built “encryption algorithm” will not be secure, regardless of how unique or “smart” or seemingly random it is. This problem has been solved. Creating your own custom solution will only make your system vulnerable. Read: Security Through Obscurity.

Do not use md5 or sha1!

The problem with hashing functions such as md5 and sha1 is that they are extremely fast! This is not a good thing, as it means that your passwords will be easier to brute force. For example: Somebody with a powerful GPU can generate billions of md5 hashes per second. How much time will pass before they eventually create a hash that matches the one that is stored in your database?

We do not encyrpt or decrypt passwords.

This is important to note. Using the term “encryption” suggests that there is a way to decrypt the password. This is not the case, as password hashing is a one-way street. i.e. Once you hash the password, you shouldn’t be able to decrypt it or figure out what the original plaintext value was.

“But how do we know if a user’s password is correct?” Well, it’s actually pretty simple. We hash the password that the user entered into the login form before comparing it against the hash that we have saved in our database. That way, we’re not actually comparing passwords. Instead, we’re comparing password hashes. If two hashes match, then we assume that the user has entered the correct password.

Use password_hash.

Luckily enough, we are now living in an era where most of the complexity behind password hashing has been abstracted away into inbuilt PHP functions and open source libraries. Nowadays, we no longer have to worry about whether or not we are generating a cryptographically secure salt. Nowadays, we can avail of functions and PHP libraries that were created by people who know a lot more than us – code that has been open sourced and vetted by the public at large.

If you’re using PHP version 5.5 or above, you can avail of the function password_hash. If you’re on an earlier version that is above 5.3.7, then you can download ircmaxell’s password_compat library.

Both of these solutions use BCRYPT as their default hashing algorithm. The great thing about BCRYPT is:

  1. It has been around for a while, which means that it has received a fair amount of scrutiny.
  2. It is slow / computationally expensive to brute force (see my point about md5 and sha1).
  3. You can adjust how computationally expensive it is by changing the algorithmic cost. This allows the algorithm to move with the times (i.e. you can adjust the cost factor according to present-day hardware capabilities).

PHP BCRYPT Example.

Here’s an example of hashing a password with BCRYPT using PHP’s password_hash function:

<?php

//The original plaintext password.
$password = 'test123';

//Hash it with BCRYPT.
$passwordHashed = password_hash($password, PASSWORD_BCRYPT);

//Print it out.
echo $passwordHashed;

NB: The $passwordHashed contains the password hash that you should be storing against the user account in question. Do NOT store the plain text password.

The great thing about the password_hash function is that it will automatically generate a random salt that is cryptographically secure! In fact, the PHP manual advises against supplying your own:

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.

What length should my password database column be?

BCRYPT will always return a string that is 60 characters in length. This means that you can use a VARCHAR(60) column. If you plan on using the default flag for the second parameter, then you should use VARCHAR(255), as that will allow you to accommodate stronger algorithms that are added to PHP in the future.

Comparing password hashes.

OK, so you’ve stored the password hash against the user account and now the user in question is wanting to login. When they enter their username and password into the login form, you will need to retrieve the password hash that has been stored against the provided username.

In this example, I am using the PDO object to retrieve the user’s details. I then use the password_verify function to compare the attempted password with the password hash that has been stored against that user:

//The form values that the user has supplied us with.
$username = $_POST['username'];
$password = $_POST['password'];

//Retrieve the table row for the given username.
$sql = "SELECT id, username, password FROM users WHERE username = :username";

//Prepare your statement.
$stmt = $pdo->prepare($sql);
    
//Bind the username value.
$stmt->bindValue(':username', $username);
    
//Execute the statement.
$stmt->execute();
    
//Fetch the table row.
$user = $stmt->fetch(PDO::FETCH_ASSOC);

//If we retrieved a relevant record.
if($user !== false){
    //Compare the password attempt with the password we have stored.
    $validPassword = password_verify($password, $user['password']);
    if($validPassword){
        //All is good. Log the user in.
    }
}

Need to learn more about the login / registration process? Read: PHP User Registration & Login Form.