Thursday, January 17, 2008

PHP HASH

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.



Good Encryption: http://drupal.org/project/phpass

1 comment:

(Victor) Xi Wang said...

rule 1: Speed is more helpful to crackers than to you.

rule 2: don't try to build your own encryption methods. Public industry methods are more robust.

rule 3: Md5 is enemy to encryption.