We all know storing passwords in clear text in your database is rude. Yet, many do it because it makes a website easy for testing or password recovery. Programmers often don’t hash passwords because we are lazy. But, I’m developing a WordPress security product. So, I should make an effort to securely hash passwords. It’s just polite really. I know many of my users will set their password as Password1, but even so, I’d like to hash passwords. However, again I’m a lazy programmer, so I went to PHP.net to grab some code to steal for this. I thought I’d be done in 10 minutes.
It didn’t take 10 minutes.
Instead I found the PHP man pages for storing passwords to be confusing, cryptic (haha get it?), and full of esoteric comments that are hard to understand. I don’t really care about the minute differences between protocols, but I found that I shouldn’t use md5. I just wanted some sample code that works. I love the PHP manual, but when it comes to passwords the man page left me with more questions than answers. So, I read through everything on StackExchange, and a few good blogs. I turned what I learned into some code so that you don’t have to read them.
Here’s the short version of what you need to know:
- Hash your passwords with something other than MD5 (Bcrypt or Sha2).
- Don’t invent your own algorithm. You will fail.
- Use a unique, random salt for each password and store both the salt and hash.
- Salt does not have to be secret. Each salt has to be unique so the hashes are unique.
- Algorithms won’t fix bad passwords.
Create a mySQL table to hold hashed passwords and random salt
-- -- SQL create script for for table `users` -- CREATE TABLE IF NOT EXISTS `users` ( `user_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, `email` varchar(30) NOT NULL, `reg_date` date NOT NULL, `fname` varchar(20) DEFAULT NULL, `lname` varchar(20) DEFAULT NULL, `salt` char(21) NOT NULL, `password` char(60) NOT NULL, PRIMARY KEY (`user_id`), UNIQUE KEY `email` (`email`) ) ;
PHP code required by both registration and validation
//ini_set("display_errors","1"); //ERROR_REPORTING(E_ALL); CRYPT_BLOWFISH or die ('No Blowfish found.'); $link = mysql_connect('localhost', 'wpscanner', 'aUvmxcxvTUPtW8Kw') or die('Not connected : ' . mysql_error()); mysql_select_db('wpscanner', $link) or die ('Not selected : ' . mysql_error()); $password = mysql_real_escape_string($_GET['password']); $email = mysql_real_escape_string($_GET['email']); //This string tells crypt to use blowfish for 5 rounds. $Blowfish_Pre = '$2a$05$'; $Blowfish_End = '$';
PHP code you need to register a user
// Blowfish accepts these characters for salts. $Allowed_Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./'; $Chars_Len = 63; // 18 would be secure as well. $Salt_Length = 21; $mysql_date = date( 'Y-m-d' ); $salt = ""; for($i=0; $i<$Salt_Length; $i++) { $salt .= $Allowed_Chars[mt_rand(0,$Chars_Len)]; } $bcrypt_salt = $Blowfish_Pre . $salt . $Blowfish_End; $hashed_password = crypt($password, $bcrypt_salt); $sql = 'INSERT INTO users (reg_date, email, salt, password) ' . "VALUES ('$mysql_date', '$email', '$salt', '$hashed_password')"; mysql_query($sql) or die( mysql_error() );
Now to verify a user’s password
$sql = "SELECT salt, password FROM users WHERE email='$email'"; $result = mysql_query($sql) or die( mysql_error() ); $row = mysql_fetch_assoc($result); $hashed_pass = crypt($password, $Blowfish_Pre . $row['salt'] . $Blowfish_End); if ($hashed_pass == $row['password']) { echo 'Password verified!'; } else { echo 'There was a problem with your user name or password.'; }