The linkedin password breach highlighted once again the risks associated
with storing user passwords. I hope you are not still storing passwords
in the clear and are using a one-way salted hash before storing them.
But, the algorithm you choose to use is also important. If you don\'t
know why, go read You\'re Probably Storing Passwords
Incorrectly.
The choice, at the moment, seems to come down to SHA512 versus
Bcrypt encryption. There\'s a
StackOverflow Q&A discussing the merits of
each.
Bcrypt gets the nod since its goal is to be slow enough that brute force
attacks would take too much time to be feasible, but not so slow that
honest users would really notice and be
inconveniencedĀ [1].
I wanted to switch one of my personal apps to use bcrypt, which on php
means using Blowfish encryption via the crypt()
function. There\'s no shortage of classes and
examples for using bcrypts to hash a string. But I didn\'t find anything
that outlined how to setup a database table to store usernames and
passwords, salt and store passwords, and then verify a login request.
Storing passwords in Mysql
To store passwords in a MySQL database, all we need is a CHAR field of
length 60. And you don\'t need a separate column for the salt, as it
will be stored as part of the password. The SQL for a minimal Users
table is shown below.
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(30) NOT NULL,
`password` char(60) NOT NULL,
PRIMARY KEY (`id`),
);
When a user registers providing a username and password, you have to
generate a salt and hash the password, before saving it. This
gist helped me figure out how to
salt and hash them.
function save_user($username, $password, PDO $db)
{
// create a random salt
$salt = substr(str_replace('+', '.', base64_encode(sha1(microtime(true), true))), 0, 22);
// hash incoming password - this works on PHP 5.3 and up
$hash = crypt($password, '$2a$12$' . $salt);
// store username and hashed password
$insert = $db->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
$insert->execute($username, $hash)
}
Authenticating Users
When a user comes back to your site and tries to login, you retrieve
their credentials and then compare the expected password to the supplied
password. Remember we were clever and stored the salt as part of our
hash in the password field? Now, we can reuse our stored password as the
salt for hashing the incoming password. If its the right password,
we\'ll have two identical hashes. Magic!
function validate_user($username, $password, PDO $db)
{
// attempt to lookup user's information
$query= $db->prepare('SELECT * FROM users WHERE username=?';
$query->execute(array($username));
if (0 == $query->rowCount()) {
// user not found
return false;
}
$user = $query->fetch();
// compare the password to the expected hash
if (crypt($password, $user['password']) == $user['password']) {
// let them in
return $user;
}
// wrong password
return false;
}
Those are the basics for using bcrypt to store passwords with PHP and
MySQL. The main difference I found, was that the hashing and comparison
of hashes now happens in PHP. With MD5 and SHA algorithms, you could
invoke them using the database functions provided by MySQL. As far as I
could find, it doesn\'t have a native Blowfish/bcrypt function. If your
system provides a crypt() call, you maybe be able to use Blowfish
encryption, but it won\'t be an option on Windows systems.