PHP Hash (bcrypt) Passwords with Random Salt

By Greg Boggs

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:

  1. Hash your passwords with something other than MD5 (Bcrypt or Sha2).
  2. Don’t invent your own algorithm. You will fail.
  3. Use a unique, random salt for each password and store both the salt and hash.
  4. Salt does not have to be secret. Each salt has to be unique so the hashes are unique.
  5. 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.';
}




All content © Copyright 2014 by Greg Boggs.
Subscribe to RSS Feed – Posts or just Comments