How to store passwords safely with PHP and MySQL
First, let me tell you how not to store passwords and why.
Do not store password as plain text
This should be obvious. If someone gains access to your database then all user accounts are compromised. And not only that, people tend to use the same password on different sites so those accounts will be compromised as well. Your site doesn’t even need to be hacked; a system administrator could easily browse your database.
Do not try to invent your own password security
Chances are that you’re no security expert. You’re better off using a solution that has been proven to work instead of coming up with something yourself.
Do not encrypt passwords
Encryption may seem like a good idea but the process is reversible. Anyone with access to your code would have no trouble transforming the passwords back to their originals. Security through obscurity is not sufficient!
Do not use MD5
Storing password hashes is a step in the right direction. Cryptographic hashing functions like MD5 are irreversible which makes it difficult to figure out the original password. To validate a hashed password, simply hash the password again when a user logs in and compare the hashes.
<?php $password = 'swordfish'; $hash = md5($password); // Value: 15b29ffdce66e10527a65bc6d71ad94d ?>
Note that this makes it impossible to retrieve a password from the database. If a user forgets his password, simply generate a new one.
So why not MD5? It is quite easy to make a list of millions of hashed passwords (a rainbow table) and compare the hashes to find the original passwords (the same goes for other hashing functions like SHA-1).
MD5 is also prone to brute forcing (trying out all combinations with an automated script) because of collisions. This means that different passwords can have the same hash, making it even easier to find one that works.
MD5 collision demo: mscs.dal.ca/~selinger/md5collision
Do not use a single site-wide salt
A salt is a string that is hashed together with a password so that most rainbow tables (or dictionary attacks) won’t work.
<?php $password = 'swordfish'; $salt = 'something random'; $hash = md5($salt . $password); // Value: db4968a3db5f6ed2f60073c747bb4fb5 ?>
This is better then using just MD5 but someone with access to your code can find the salt a generate a new rainbow table.
What you should do
- Use a cryptographically strong hashing function like SHA-1 or even SHA-256 (see PHP’s hash() function).
- Use a long and random salt for each password.
- Use a slow hashing algorithm to make brute force attacks near impossible.
- Regenerate the hash every time a users logs in.
<?php $username = 'Admin'; $password = 'gf45_gdf#4hg'; // Create a 256 bit (64 characters) long random salt // Let's add 'something random' and the username // to the salt as well for added security $salt = hash('sha256', uniqid(mt_rand(), true) . 'something random' . strtolower($username)); // Prefix the password with the salt $hash = $salt . $password; // Hash the salted password a bunch of times for ( $i = 0; $i < 100000; $i ++ ) { $hash = hash('sha256', $hash); } // Prefix the hash with the salt so we can find it back later $hash = $salt . $hash; /* Value: * e31f453ab964ec17e1e68faacbb64f05bccceb179858b4c482c1b182ff1e440e * f1e10feb5b86c6d367e4eb8f90f2cde5648a7db3df8526878f20a77eed00c703 */ ?>
In the above example we turned a reasonably strong password into a 128 characters long hash that we can store in a database. The next time the user logs in we can validate the password as follows:
<?php $username = 'Admin'; $password = 'gf45_gdf#4hg'; $sql = ' SELECT `hash` FROM `users` WHERE `username` = "' . mysql_real_escape_string($username) . '" LIMIT 1 ;'; $r = mysql_fetch_assoc(mysql_query($sql)); // The first 64 characters of the hash is the salt $salt = substr($r['hash'], 0, 64); $hash = $salt . $password; // Hash the password as we did before for ( $i = 0; $i < 100000; $i ++ ) { $hash = hash('sha256', $hash); } $hash = $salt . $hash; if ( $hash == $r['hash'] ) { // Ok! } ?>
A few additional tips to prevent user accounts from being hacked:
- Limit the number of failed login attempts.
- Require strong passwords.
- Do not limit passwords to a certain length (remember, you’re only storing a hash so length doesn’t matter).
- Allow special characters in passwords, there is no reason not to.
That’s it, happy coding!
Filed under Programming