PHP Password Security
10th October 2007
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.
Tags: Best practice, Free code, PHP, Security

9 Comments add your own
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!
Dashifen | 10th October 2007 at 17:30
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.
richard | 10th October 2007 at 17:47
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.
5566 | 24th October 2007 at 03:22
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.
richard | 24th October 2007 at 08:48
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
Ben | 6th November 2007 at 04:33
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.
returns true if $password was used to create $hash and false if it was not.
richard | 6th November 2007 at 12:43
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.
solar | 8th November 2007 at 21:21
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.
Nyna | 1st December 2007 at 01:01
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.
richard | 1st December 2007 at 11:58
Leave a Comment
XHTML: you can use these tags - <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>Subscribe to the comments via RSS Feed