Password Hashing with Bcrypt and PHP (August 25, 2013)

I recently started rewriting the management section of my website, and I wanted to beef up my user authentication. I started by looking for a Bcrypt implementation in PHP. As it turns out, PHP 5.5 introduced a new, dead-simple password crypt API that uses Bcrypt by default. No more messing with generating salts, doing hashing rounds, or figuring out which arcane incantations of PHP will get you the hash you're looking for. The new crypt API takes care of all of it for you. That's when I realized Debian 7 only ships with PHP 5.4.4, which happens to be the distribution running on my little Linode VPS. Luckily, I discovered a forward-comparability layer for the new crypt API, supporting PHP versons 5.3.7 and greater.

Writing the SQL

The first thing we need to do is setup a simple user table. I momentarily debated whether to use usernames or email addresses, but decided the small volume of user accounts I was expecting didn't justify all the extra verification steps associated with using an email address. Thus, my SQL code looked like this:

MySQL code for creating a user table

The Bcrypt implementation in PHP only outputs 60 characters total, but you'll notice the 'hash' column is a varchar 255. This is for forward compatibility. If, at some point in the future, Bcrypt is replaced by the next great hashing algorithm, the new crypt API supports transparently migrating to the new algorithm. It's not unreasonable to assume future algorithms will output more than 60 characters, so we are just planning for that eventuality now.

Writing the PHP

The PHP needed to create a user is pretty simple. Here is some rough code to get you started:

PHP code for creating a new user

You can set a lot of options, but I'll only cover a few here. 'PASSWORD_DEFAULT' is the same as 'PASSWORD_BCRYPT' in PHP 5.5. You can also pass in the work factor Bcrypt should use as part of a third "options" parameter to password_hash(). The higher the work factor, the longer it will take to hash passwords, increasing the amount of time it would take someone to crack them.

The code for logging in might look like this:

PHP code for logging in a user

Notice I didn't have to specify the algorithm, salt, or work factor when calling password_verify(). This information is encoded in the string returned from password_hash(). This means, you can change work factors or even algorithms simply by passing different parameters to password_hash(), without breaking existing users.

I'm using PHP sessions to track users, so making sure a user is logged in is as easy as the following:

PHP code for revalidating a user

And, finally, logging out:

PHP code for logging out a user

Final Thoughts

Mix this with HTTPS on your website, and you've got a simple, secure way to do user authentication. Easy as pie, and who doesn't love pie?