Storing Passwords the Right Way

Spread the news

How should passwords be stored? The short answer is: DON’T! I see countless posts on reddit and around the web from people who are trying to figure out how to use PHP’s “new” password functions.  These new functions are awesome in that they have finally made it so that those who are not security specialists can start managing passwords the right way.  PHP’s password functions do things the right way and give us a means by which to ensure our sites can continue to stay secure – even as the red team closes in, coming up with new ways to break the systems.

Most security articles actually tell you types of algorithms to use and setup for them all.  However, this means you will need to constantly watch out for, and change your algorithm list as things go.  Most people don’t have time for this, and end up putting it off, which puts your users’ data at risk.

I’ve heard people try to make excuses for not keeping up on their user password security saying that they will never get hacked because they don’t have important user data.  However, that’s not how hackers work.  The red team knows that most people use the same password everywhere, which means that if they can get your user’s password and their email address, they can probably now get into their email.  Once a user’s email has been hacked, the hacker can use it to reset the passwords on their other, more juicy accounts (those with important data or access to their money). If you have users, you owe it to them to take care of their password.

In the past it was common practice to use MD5 to hash a password and store that. Developers claimed over and over that they don’t store the users’ passwords – just hashes which are not reversible…  and everyone believed them.  Some still believe this to be true. However, MD5 is no longer secure.  It can be hacked in a matter of minutes with just the power of a cell phone. PHP provides a simple FAQ about this and does a great job at explaining why you should just use the native password system built into PHP since v5.5. However, it can take some time to read through all the docs to get a full idea of what needs to be done even though it’s actually really simple.

Successful password management is all about making it difficult to brute force finding a password.  This is done by two things – the salt and the algorithmic cost.  Most algorithms  allow you to dynamically change cost by increasing the number of times the algorithm is run on the data.  The more time it takes to run the long it will take to find the password by brute force. Since the algorithm and its cost can change over time, the hash store must also contain information about them.  PHP takes care of this for us by including all the data needed to check the password right in the hash.  This also means that as things change you can tell that a password hash is outdated (and update it). This functionality makes PHP’s password system extremely handy.

Preliminary tasks

The first thing you will need to do is get things set up for the new system.  Assuming you are storing the hash in a database, you will need to create a single column to hold it.  Right now the length of the string is up to 60 characters, but a field length of 255 is recommended so that password hashes can take more space as technology changes.

In addition to a place to store the hash, you will need to get some information from your server.  The password features will default to a cost of 10 if not configured, and could increase with time, though that is not discussed in the RFC.  However, it is recommended that you determine the best cost based on your server hardware.  This is a simple script provided on the password_hash page to determine the cost for your own server:

<?php
/**
 * This code will benchmark your server to determine how high of a cost you can
 * afford. You want to set the highest cost that you can without slowing down
 * you server too much. 8-10 is a good baseline, and more is good if your servers
 * are fast enough. The code below aims for ≤ 50 milliseconds stretching time,
 * which is a good baseline for systems handling interactive logins.
 */
$timeTarget = 0.05; // 50 milliseconds 

$cost = 8;
do {
    $cost++;
    $start = microtime(true);
    password_hash("test", PASSWORD_DEFAULT, ["cost" => $cost]);
    $end = microtime(true);
} while (($end - $start) < $timeTarget);

echo "Appropriate Cost Found: " . $cost;

As you can see in the comments, this code assumes and suggests a 50 millisecond time limit.  Others suggest going up to 100 milliseconds, but anything more would cause the login screen on your site to start taking a long time to load.  Most users won’t complain about the login taking a little bit longer, assuming the rest of your site is fast. Personally, I suggest changing line 9 to $timeTarget = 0.10; for a cost at ≤ 100 milliseconds.  This ensures we are ahead of the game as time passes.  I also recommend that you revisit this script any time your server changes (if you’re in the cloud, this could be pretty often). You might want to put a calendar reminder for 2x a year – just run the script and update your cost.

The PHP

Now that we have a place to store the password and an appropriate hash cost, we can implement the actual password process.

In order to get a password, all we need to do is pass the password into the password_hash function exactly as seen above in the cost determination script on line 15. All you do is change the first variable to the password that you want. This function will return the full hash (with all its extra data) to be stored in your database: $hash = password_hash($password, PASSWORD_DEFAULT, ["cost" => $cost]);

Once a user submits their username/password, we will have to retrieve the password that corresponds with the user.  A simple query for this would be something along the lines of SELECT password FROM users WHERE username='username';. Don’t forget to either use a statement or escape the input so you don’t open up other security issues!

Now we can easily check the password entered with the existing hash.  Fortunately, the hash that is stored in the database has everything needed – all we do is pass the password entered and the hash from the database into the password_verify function. It will return true/false depending on the results! $correctPassword=password_verify($password,$hash);.

Now we have one more thing to implement.  As times change, so will algorithms and the cost option.  In order to determine whether or not the hash needs to be updated, we can implement the password_needs_rehash function. This function will look at the metadata in the hash and compare it to the current settings.  If the hash that is passed in is less secure than the current desired settings, it will return true.  This indicates that you should go ahead and use the password they just entered to generate a new hash, and update the database with it. Altogether this looks like this (taken from php.net and modified for semantics):

<?php

$password = $_POST['p'];
$hash = '$2y$10$YCFsG6elYca568hBi2pZ0.3LDL5wjgxct1N8w/oLR/jfHsiQwCqTS'; // get this from the database!

// The cost parameter can change over time as hardware improves
$options = ['cost' => $cost];

// Verify stored hash against plain-text password
if (password_verify($password, $hash)) {
    // Check if a newer hashing algorithm is available
    // or the cost has changed
    if (password_needs_rehash($hash, PASSWORD_DEFAULT, $options)) {
        // If so, create a new hash, and replace the old one
        $newHash = password_hash($password, PASSWORD_DEFAULT, $options);
        // save $newHash to the database
    }

    // Log user in

}

All of the code on this page comes straight from php.net, but there it is distributed among the various password_… functions. Hopefully this article helps to bring all the functions together into one understandable concept. I’d be happy to answer any questions you might have about password management below in the comments! Also, let me know if you have any additional tips and tricks!


Spread the news

2 Comments

    1. Apple follows a good security pattern for storing these passwords as outlined here: https://www.apple.com/business/docs/iOS_Security_Guide.pdf

      Personally I prefer 1Password since I can move my encrypted password vault to any system I need, including non-apple products. They also provide a very good security model: https://1password.com/files/1Password%20for%20Teams%20White%20Paper.pdf

      Regardless of how a user keeps track of their own passwords – in their mind, in a password vault, or on a piece of paper – it is imperative for site developers to do everything we can to ensure their chosen password is not ever transmitted by our own negligence to a third party.

Leave a Reply

Your email address will not be published. Required fields are marked *