PHP Password Security

If you build websites that require users to register it’s your responsibility to keep their passwords safe. And if you’re storing the passwords in plain text then you’re not doing your job properly. It may be that, like Reddit, you think that storing passwords in plain text leads to a better user experience. I happen to agree with you. But then, like Reddit, what happens if your database is stolen? It’s not just your site that is compromised. Since most users use the same password on multiple sites, all those sites have also been compromised.

No data is entirely secure, and if anyone else has access to your webserver (the company managing the server for you?) or your database (the company storing the backups?) then you don’t have total control over the security anyway. So there’s always a chance your database could be stolen. So, the simple rule is to hash your passwords.

Hashing

A hash is a string derived from the original password via a one-way algorithm. In other words, it’s easy to create the hash from the original, but harder (when used for security, ideally impossible) to create the original from the hash. You store the hash in the database, and when the user signs-in you hash the password they sign-in with and compare it to the hash in the database. Something like this

if( $user->passwordhash == sha1( $_POST['password'] ) )

That way, you never store the user’s password.

There are a number of hashing algorithms in PHP, of which md5 and sha1 are the most commonly used. Unfortunately, neither is as secure as they were once thought to be. It would be better to use a more secure hash, and if you have the Hash engine in your PHP installation (included by default since PHP 5.1.2) then you have access to many more algorithms. So a better example would be

if( $user->passwordhash == hash( 'whirlpool', $_POST['password'] ) )

Rainbow tables

But there’s another problem. Once your database is stolen, the thief has plenty of time to crack the passwords using a simple Rainbow Table attack. This involves creating a large selection of hashes based on likely passwords (e.g. every word in the dictionary) and then comparing the hashes with the hashes in your database. Within an hour or so, half the passwords in your database will probably have been cracked.

To prevent this you should salt each password by adding a random string to it (called a salt or nonce). The time consuming part of a rainbow table attack is building the dictionary of hashes. Adding a random salt to the password means the thief has to build a whole new dictionary of hashes for each salt, making a rainbow table attack too time consuming to be viable. Each password should have a different salt, and the salt doesn’t even need to be secret.

The Code bit

So, for secure passwords you need code that looks something like this

// get a new salt - 8 hexadecimal characters long
// current PHP installations should not exceed 8 characters
// on dechex( mt_rand() )
// but we future proof it anyway with substr()
function getPasswordSalt()
{
    return substr( str_pad( dechex( mt_rand() ), 8, '0',
                                           STR_PAD_LEFT ), -8 );
}

// calculate the hash from a salt and a password
function getPasswordHash( $salt, $password )
{
    return $salt . ( hash( 'whirlpool', $salt . $password ) );
}

// compare a password to a hash
function comparePassword( $password, $hash )
{
    $salt = substr( $hash, 0, 8 );
    return $hash == getPasswordHash( $salt, $password );
}

// get a new hash for a password
$hash = getPasswordHash( getPasswordSalt(), $password );

You don’t have to attach the salt to the hash, you can instead store them separately within the database, but I like keeping them together in a single string. Equally, the salt needn’t be in hexadecimal, but I like the symmetry with the hexadecimal hash.

Finally, as Thomas Ptacek points out, you don’t want the fastest hash algorithm in the world for this – a fast algorithm is more useful to an attacker than it is to you.

30 thoughts on “PHP Password Security

  1. A question for you: if I’m understanding the code above, the salt is simply some random information that you’re prepending to the password prior to hashing it. Did you arbitrarily choose a length of eight hex characters for your salt? Would it increase security in any capacity to choose a salt of variable length? Clearly, you’d that length would need to be stored, probably in the database, so that the original password could be recovered. Thanks!

  2. Having a variable length salt makes little difference. However, the length of the salt does – the salt should be long enough to provide sufficient variety. With just 8 hexadecimal characters there are 4294967296 possible salts. So if, for example, it takes one hour to construct a full rainbow table for a single salt it will take 4294967296 hours (or 489,957 years) to construct rainbow tables for all our salts. That seems long enough to me.

  3. To Dashifen: I think it’s unnecessary to have variable length. That makes the password verification process harder. You can’t simply substr the salt out because you don’t know the length. If you store this kind of information in another database, but that database may be stolen, too.

  4. Because the hash is always the same length, it’s easy to find the length of the salt (length of salt plus hash minus length of hash). Personally I see no significant advantage in using a variable length salt, but also no particular disadvantage either.

  5. Hello,
    I am a beginner when it comes to this but i do hope to ensure that my systems are secure:

    So i can use $hash = getPasswordHash( getPasswordSalt(), $password );

    to get my new hash (with salt) which i can then put into the mysql database.
    I dont know what comparepassword function is for?

    so i know the password is hashed now lets say that the user want to change the password, in order to change it they need to i guess enter in the old password then in the new password, how do i get the old password from the hash?..

    Ben

  6. Hi Ben

    You can’t get the password from the hash. That’s the point of it. What you can do is recreate the hash from the password and see if you get the same hash. That’s what comparePassword is for.

    comparePassword( $password, $hash );

    returns true if $password was used to create $hash and false if it was not.

  7. May I plug this one? –

    phpass – portable public domain password hashing framework for use in PHP applications

    The preferred (most secure) hashing method supported by phpass is the OpenBSD-style Blowfish-based bcrypt, also supported with our public domain crypt_blowfish package (for C applications), and known in PHP as CRYPT_BLOWFISH, with a fallback to BSDI-style extended DES-based hashes, known in PHP as CRYPT_EXT_DES, and a last resort fallback to an MD5-based variable iteration count password hashing method implemented in phpass itself.

    phpass does not require any extra libraries to be linked with PHP or anything like that. It will work in any hosting environment with PHP.

    Oh, and there’s really no problem with using MD5 as a building block. The fact that MD5 has been broken (in certain ways) is irrelevant to its uses for password hashing, which it was never intended for anyway. The real problem is that MD5 is way too fast; this one is dealt with by the use of multiple iterations. Of course, CRYPT_BLOWFISH and CRYPT_EXT_DES are still preferable over the iterated and salted MD5 – primarily because they’re native C (or sometimes even assembly) implementations, which allows for the use of larger numbers of iterations of the underlying cryptographic primitives.

  8. Whilst I agree in principle with the ideas of using hashing, no matter what happens it still suffers from the same problem – that of collisions.

    Given a plain text password of (say) 8 alpha-numeric characters, a salt of (say) 63 alpha numerics and using the whirlpool hash, we end up with a nice 128 character (0-9, A-F) hash.

    Now mathematically it is guaranteed that there are collisions with a different password. It matters not how long the salt is or indeed how long the password.

    This would mean that, assuming you were very lucky, a simple one letter password would generate a collision. It doesn’t matter that the passwords don’t match, only that the hashes do.

    So, how to circumvent this issue – personally, I use blowfish encryption rather than hashing as there are no collisions in this field.

    Okay, so what happens if someone finds the blowfish key? Well for that to happen, they will have had to bypass the servers security and find the key wherever it may be stored – as complex as breaking in and finding the salt, however with encryption, there is no possible collisions making the whole system more secure.

  9. Nyna

    Yes, due to potential collisions, a dictionary attack via the web interface is very slightly less secure than otherwise. But such attacks should prevented by other means. If you don’t protect against dictionary attacks then your site is vulnerable whether you use a hash or not, and if you do then it’s secure whether you use a hash or not.

    The purpose of the hash is to protect against other types of attack that involve direct theft of your data.

    You misunderstand the salt. Unlike a blowfish encryption key, the salt does not need to be secret for the hash to be secure. It only matters that the salt is used and is different for each password. The salt doesn’t protect against decryption (the nature of the hash algorithm does this), it protects against a rainbow table attack (a variant on a dictionary attack for hashed values).

    Most companies who have lost their databases considered their security to be very good, yet the databases were still stolen. A similar level of security exists for your encryption key. If a criminal does access your server then they can access both your database and your encryption key.

    And don’t assume hacking your production server is the only way in – Do you have backups or development servers that hold the code that contains the encryption key? How many employees know or have ready access to the encryption key? And do any other people have physical access to one of these systems?

    With the hash algorithm above, criminals cannot find the passwords by hacking the server or stealing backups. Even I can’t find my users’ passwords.

  10. The great thing is in your code is to create a second variable to hinder decryption.

    If I understand your code, you will need to make one request to the database to get the Salt before comparing the password.

    On my system I use the following idea.
    1 – make only a single request to the database
    2 – use two variables – (password and user)
    3 – treat the password with different hash to minimize the collisions
    5 – encourage the use of non-alphanumeric characters.
    4 – Create a final string much larger than the hash to became the original data impossible to find.

    some thing like this:

    function goodLuckToFoundThePass($user, $password){
    $sha = md5($user).sha1('%&some^&name$/here'.$password, 'a').md5(strrev($user));
    $half = round(strlen($sha)/2);
    $sha1 = substr($sha, 0,$half);
    $sha2 = substr($sha, $half);
    $sha3 = $sha1 . 'the best way to create a secure password is making final string much larger than the hash to became the original data IMPOSSIBLE to find' . $sha2;
    $NewPassword = md5($sha3.$sha1, 1).md5($sha3.$sha2, 1);
    return $NewPassword;
    }


    Collisions? Maybe. 1 for pow(2,512);
    but the ideia here is protect your users.

    Ps: Sorry my English.

  11. I only have to make one request to the database to check the user’s login. The salt is returned as part of that request, along with the salted & hashed password.

    I’m not clear of the value of your convoluted algorithm over simply using a good, robust hash algorithm.

  12. I totally agree with you about functional speed. The problem is all these algorithms have been developed/designed for speed and there is always gonna be a faster computer coming out next year, not to discount the mainframes of today. Personally I like exponential algorithms which can really tick down CPU and also involving various types available which is problematic if you are depending on what only comes with the package. Ideally it means a separate executable which PHP calls on to hash and then having an event to signal authenticity not to mention the timeout. If the result is as randomized as possible, and the executable is safe from reverse engineering. That’s basically all you can do. It does mean though that, commercially it’s not viable to release without compromising it’s security to possible disassembly. Best developed in-house, kept in-house and hush, don’t tell anyone.

  13. Sorry, i dont get it. You mix a random string with the password, hash them and when you try to compare the password again, you generate another random value and hash it with the password?

    If your random strings are not equal (which should be the case in most of the times) how do you get the same hash value again?

  14. Peter, you store the hash and the random string in the database, then use the same random string when comparing. I concatenate the random string with the hash when saving it, then split them apart again when comparing.

  15. Ah thanks a lot, i did not see that the salt is added to the hash again. Excellent article!

  16. In addition, it is important to remember that every login request preferably should be over SSL or otherwise the password must not be transmitted in clear text (for example, instead of using SSL the password could be hashed with Javascript by the client using the salt stored in the DB on the server (the server supplies the salt on AJAX request) and then this hash should be hashed (using the same or another hash method, the salt should this time be randomized by the server for every login attempt) before transmitted to the server).

  17. A question. I have seen lots of questions but no real answers to this one for php security.

    I hate placing the connection information for the mysql database connection in any form of plain text, even in my code.

    It seems like there “ought” to be a way to use hashing etc. to create a more secure method, perhaps pulling the connection info from a file with the file only storing the hashed key, etc. and pulling the salt from a completely separate locale…

    Thoughts?

  18. wouldnt it be better to do

    return $salt . hash($algo, $pass . $salt . $pass)

    instead of just $salt . hash($algo, $salt . $pass)?

    that way the attacker won’t even figure out were the (2nd) salt is?

  19. I have the same question as “Php database password”.

    If we do all the work to protect passwords, etc, and not protect our database. Then once it’s stolen, it’s probably a matter of time that passwords are cracked.

    So if connection info to a database is in plan text (in a php file), someone steals your code. He will know what info to use to get connection to your db, then does what ever he wants to it!

    How do you protect your database? Not just the data in the database.

  20. Ottoman – The whole point of this method is that even with full access to the database and your code, the passwords can’t be cracked in any reasonable time span (i.e. it would take years).

    If the database is stolen then the rest of the data in your database would be obtained but not the passwords.

  21. Pingback: Password Security

  22. It is very important that your hashing be slow. You could slow down the hashing function by up to 1 or 2 seconds and this would make it endless to randomly match hashes with salts. My question is can Iuse the php function sleep() to pause the program for a while?

  23. Jonathan – There’s no point in artificially slowing the hash algorithm. Since a rainbow table attack is run on the attacker’s computer, they can use the hash algorithm without your sleep() pause so artificially slowing it is no help. To get a slower hash, use a more complex algorithm. e.g. Use whirlpool rather than md5.

  24. Well, sleep() have its place in a login system.
    When a login tentative fail, one can randomly sleep N seconds to slow down any bruteforce attempt.

  25. I see that raises another question. Beyond passwords, how much sensitive data should be encrypted/decrypted so if the db is stolen select critical data is safe (relatively). I do understand we’re not talking about 2-way enc/dec here, but it makes me think.

  26. From what I have researched, in addition to using SSL, currently the best security measure for storing passwords is using a generated Hash from the algorythm called “Whirlpool” and salting it with a fixed-length randomly generated string each time a password is created, coupled with a strong password policy (Upper/Lower Case, Include Numbers and Puncuation), as well as using secure database access methods (PDO, prepared statements) and keeping said connection information in as a secure and protected area as possible. And in addition, though breakable, it would be wise to use a Cryptography method for storing sensitive user info like emails. Considering this is an older post now, I am requesting confirmation of this as I am trying to formulate the best security policy/procedure as possible currently.

  27. Pingback: Code Snippets - php: creating stronger passwords, even better

  28. TommyRay – That sounds reasonable, although I favour length rather than variety of characters for password strength. The password “This is my very secret password” is likely stronger than “h5G%i*f1”, not least because the user can remember the first without writing it down.

  29. Can you suggest a secure method for login sessions? Your code does very well for authenticating, but Now I need to find a good way to keep the user logged in for some time without compromising security.

Leave a Reply

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