Password Hashing In PHP

Sandeep Panda
Tweet

Most modern PHP applications access important user information and store them in a database. For example, web app might have a registration system for new users. But how should you store usernames and passwords in the database?

You must always think about security. If passwords are stored in plain text, what happens if an attacker gains access to your database? He can easily read all of the users’ passwords. That’s why we use a technique called password hashing to prevent attackers from getting user passwords.

In this article you’ll learn how to store the passwords securely in the database so that, even if your database falls into wrong hands, no damage will be done.

What Is Password Hashing

Hashing is not a new concept. It has been in practical use for quite a long time. To understand hashing, think about fingerprints. Every person has a unique fingerprint. Similarly, each string can have a unique fixed-size “digital fingerprint” called a hash. For a good hashing algorithm, it’s very rare that two different strings will have same hash (called a collision).

The most important feature of hashes is that the hash generation process is one way. The one way property indicates that it’s impossible to recover the original text from its hash. Therefore password hashing perfectly suits our need for secure password storage. Instead of storing a password in plain text, we can hash the password and store the resulting hash. If an attacker later gains access to the database, he can’t recover original password from the hash.

But what about authentication? You can no longer compare the password entered by user in a login form with the hash stored in the database. You need to hash the login password and compare the result with the hash stored in the database.

How Hashing Is Done In PHP

There are different algorithms for generating hash of a text. The most popular ones are: MD5, SHA1, and Bcrypt. Each of these algorithms are supported in PHP. You really should be using Bcrypt, but I’ll present the other alternatives first because they help illustrate what you need to do to protect your passwords.

Let’s start with PHP’s md5() function which can hash passwords according to the MD5 hashing algorithm. The following example demonstrates the registration process:

<?php
$username = $_POST["username"];
$password = $_POST["password"];

// create connection to database
// ...

// sanitize the inputs
// ...

// create an MD5 hash of the password
$password = md5($password);

// save the values to the database
$sql = "INSERT INTO users (username, password) VALUES (:username, :password)";

$stmt = $db->prepare($sql);

$stmt->execute(array(
    ":username" => $username,
    ":password" => $password
));

And the following example shows the authentication process:

<?php
$username = $_POST["username"];
$password = $_POST["password"];

// create connection to database
// ...

// sanitize the input
// ...

// create an MD5 hash of the password
$password = md5($password);

// retrieve the information from the database
$sql = "SELECT * FROM users WHERE username=:username AND password=:password";
$stmt = $db->prepare($sql);
$stmt->execute(array(
    ":username" => $username,
    ":password" => $password
));

$row = $stmt->fetch();

In the above example, md5() creates a 128-bit hash out of the given password. It’s also possible to use sha1() instead of md5() which produces a 160-bit hash (which means there’s less chance of a collision).

If you generate an MD5 hash of the string “MySecretPassword” and output it to the browser, it will look like the following:

7315a012ecad1059a3634f8be1347846

“MySecretPassword” when hashed with SHA1 will produce the following output:

952729c61cab7e01e4b5f5ba7b95830d2075f74b

Never hash a password two times. It does not add extra security; rather, it makes the hash weak and inefficient. For example, don’t try to create an MD5 hash of a password and then provide it as input to sha1(). It simply increases the probability of hash collisions.

Taking Password Hashing to the Next Level

Researchers have found several flaws in the SHA1 and MD5 algorithms. That’s why modern PHP applications shouldn’t use these two hash functions. Rather, they should use hash algorithms from the SHA2 family like SHA256 or SHA512. As the name suggest, they produce hashes of length 256 and 512 bits. They are newer and considerably stronger than MD5. As the number of bits increase, the probability of a collision decreases. Either of the above two is more than sufficient to keep your application secure.

The following code shows how to use SHA256 hashing in PHP:

<?php
$password = hash("sha256", $password);

PHP offers the built-in function hash(). The first argument to the function is the algorithm name (you can pass algorithm names like sha256, sha512, md5, sha1, and many others). The second argument is the string that will be hashed. The result it returns is the hashed string.

Paranoia is Good – Using Salts for Added Security

Being paranoid about the security of your system is good. So, let’s consider another case here. You have hashed the user’s password and stored it in the database table. Even if an attacker gets access to our database, he won’t be able to determine the original password. But what if he checks all the password hashes with one another and finds some of them to be same? What does this indicate?

We already know two strings will have same hash only if both of them are same (in the absence of any collisions). So if the attacker sees same hashes, he can infer that the passwords for those accounts are same. If he already knows the password to one those accounts, then he can simply use that and gain access to all of the accounts with the same password.

The solution is to use a random number while generating the hash, referred to as salt. Each time we generate hash of a password, we use a random salt. You just need to generate a random number of a particular length and append it to the plain text password, and then hash it. In this way, even if passwords for two accounts are same, the generated hashes will not be same because the salts used in both cases are different.

The following demonstrates the use of salt:

<?php
define("MAX_LENGTH", 6);

function generateHashWithSalt($password) {
    $intermediateSalt = md5(uniqid(rand(), true));
    $salt = substr($intermediateSalt, 0, MAX_LENGTH);
    return hash("sha256", $password . $salt);
}

To create a salt we use the uniqid() function. The first argument is rand() which generates a random integer. The second argument is true to increase the chance of the generated number being unique.

To authenticate the user, you must store the salt used for hashing the password (It’s possible to store the salt in another column in same table where you have username and password stored). When the user tries to login, append the salt to the entered password and then hash it with the hash function.

Going Even Further: Using BCrypt

Learning about MD5/SHA1 and salts are good to gain an understanding of what’s needed for secure storage. But for implementing a serious security plan, Bcrypt is the hashing technique you should be using.

Bcrypt is based on the Blowfish symmetric block cipher. Ideally, we want a hashing algorithm to work very slowly for an attacker’s automated cracking attempts but not too slow that we can’t use it in real world applications. With Bcrypt, we can make the algorithm work n times slower while adjusting n in such a way that it won’t exceed our resources. Also, if you use Bcrypt then there is no need to store your salts in the database.

Let’s have a look at an example that uses the crypt() function to hash the password:

<?php
function generateHash($password) {
    if (defined("CRYPT_BLOWFISH") && CRYPT_BLOWFISH) {
        $salt = '$2y$11$' . substr(md5(uniqid(rand(), true)), 0, 22);
        return crypt($password, $salt);
    }
}

The above function checks whether the Blowfish cipher is available through the CRYPT_BLOWFISH constant. If so, then we generate a random salt. The requirement is that the salt starts with “$2a$” (or “$2y$” see this notice on php.net) to indicate the algorithm is Blowfish, followed by a two digit number from 4 to 31. This number is a cost parameter that makes brute force attacks take longer. Then we append an alphanumeric string containing 22 characters as the main portion of our salt. The alphanumeric string can also include ‘.’ and ‘/’.

Now it’s time to authenticate users:

<?php
function verify($password, $hashedPassword) {
    return crypt($password, $hashedPassword) == $hashedPassword;
}

Notice that we don’t need the salt for the password when authenticating because its part of the hashed output.

For more information on Bcrypt, and why should be using it, see Callum Hopkin’s article Why You Should Use Bcrypt to Hash Stored Passwords.

Summary

An important security measure to follow is always hash your users’ passwords before storing them in your database, and use modern hashing algorithms like Bcrypt, sha256, or sha512 to do so. When you do, even if an attacker gains access to your database, he won’t have the actual passwords of your users. This article explains the principles behind hashing, salts, and Bcrypt.

Image via Fotolia

And if you enjoyed reading this post, you’ll love Learnable; the place to learn fresh skills and techniques from the masters. Members get instant access to all of SitePoint’s ebooks and interactive online courses, like The PHP Anthology: 101 Essential Tips, Tricks & Hacks.

Comments on this article are closed. Have a question about PHP? Why not ask it on our forums?

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://ba.rrypark.in Barry Parkin

    md5() unsalted is a terrible idea for a password. Rainbow tables are far too easy to generate for this.

    Bcrypt is excellent and is by far the best piece of advice in this article. It’d be useful to mention the password_hash() function available in php5.5 (http://php.net/manual/en/function.password-hash.php).

  • http://www.psinas.com Martin Psinas

    When it comes to generating random numbers, you really should be using a function like openssl_random_pseudo_bytes(). The method you’re demonstrating is based on time which makes the salts that much more predictable.

  • http://www.woodst.com/ Robert

    This isn’t necessarily aimed at you, personally, Sandeep, but I really think the warnings you gave against MD5/SHA1 should have been in big, bold letters.

    If you have a choice in what algorithm you use to hash passwords, choose Bcrypt every time. There is no reason not to go with its added security and it isn’t any more complicated. And it can be future-proofed, unlike MD5/SHA1/SHA2, and passwords can be “upgraded” over time with higher cost computations as users log in. Even SHA2 will eventually be broken.

    If you do use a hash like MD5/SHA1/SHA2 and don’t use PBKDF2 with hash_hmac(), then you’re being irresponsible. There are known attacks that let you pick up a partially completed hash and then continue adding characters on the end. In other words, if they figure out the first half they can cheaply figure out the second. Second, one of the latest known research papers listed a 16U server that can compute almost 200 BILLION MD5 hashes, 160 BILLION SHA1 hashes, and only 70,000 bcrypt hashes PER SECOND. If you’re worried about bcrypt, increase the cost to 18 or 20. It’ll take a bit of effort for users to log in, but it’ll be much harder to crack.

    I wrote a fairly extensive article on PHP’s best practices for hashing passwords: http://stackoverflow.com/questions/401656/secure-hash-and-salt-for-php-passwords/401684#401684

  • Dinko

    Why not just use phpass?

  • http://josephscott.org/ Joseph Scott

    I’m going to second what Robert has said. Honestly I was surprised to even see md5 and sha1 mentioned in any way that didn’t include “don’t ever use these to hash passwords”. For most people the answer is to go use phpass – http://www.openwall.com/phpass/ – which takes care of bcrypt for you and perhaps in the future the newer built in password hashing functions for PHP when they are in a well tested and stable release.

    One final note:

    We already know two strings will have same hash only if both of them are same (in the absence of any collisions).

    This isn’t exactly true. In the case of bcrypt it only uses the first 72 characters of a string for the hash. If you took two 150 characters strings, that both started with the same 72 character strings, you’d end up with different hashes, but they’d be interchangeable in regards to authentication.

  • ACoders

    I suggest that you use ircmaxell’s password compat librarry, its backwards compatible library for the new PHP > 5.5 password functions.
    https://github.com/ircmaxell/password_compat

  • http://blijtech.com anshuman

    “Researchers have found several flaws in the SHA1 and MD5 algorithms. That’s why modern PHP applications shouldn’t use these two hash functions”
    can you please mentions what are flaws? this would better complete this article

    • http://www.damiengrass.com Damien

      There are several flaws. Most of which were not released, for obvious reasons. Cryptographers just noticed vulnerabilities in their functioning. I guess one of the flaws is that both functions are susceptible to rainbow tables. These flaws were found way back in 2004/2005 though. The functions aren’t even worth mentioning anymore.

  • http://popsypedia.org Popsana Barida

    This is a really nice article. There are also very good comments about MD5 and SHA1.
    In most web applications today, users are authenticated by two or more parameters. Encrypting the password is the best practice today. I believe that generating salts for encryption can be done using any of the other login parameters (e.g. username or email address). Anyone who’s security conscious should be ready to do some extra work especially when working with open-source technology.

  • mark

    To be honest this article shouldn’t even show MD5/SHA1 ect as an option. You shouldn’t be showing people how to use it or even letting people know they exist. A newbie could come along quite easily just read the first part of this article, copy and paste the code and hope for the best.

    Should just stick with the BCRYPT. Yes there are a few other new kids on the block but they are yet to withstand any ‘serious’ tests on there integrity.

    • Rob

      Why shouldn’t the author mention the weaker hashing methods? If a newbie is coming here, copying and pasting hash examples rather than reading and digesting the information in this article, then the newbie shouldn’t be writing a login in the first place. It think it’s important for anybody, new or experienced in PHP, to understand the history of hash methods and why some shouldn’t be used. It helps explain why the latest method is necessary.

  • KNL

    Great article Sandeeb, thanks! And I think it’s great that MD5 and SHA1 are mentioned for comparison.

  • http://Hikari.ws Hikari

    Very nice article! Recently I was in need to encrypt passwords and had never read about it. I used PBKDF2, but the implementation I took is buggy, and sometimes I pass same data to encrypt and decrypt and it generates different values! I’ll try phpass and bcrypt!