Written by Anthony Chambers , and read 29,520 times
A little background before we get into the main article:
Storing your user's passwords at rest has always been a sensitive subject. Many developers start out storing passwords in plain-text, unaware of the pitfalls of doing so. Some even appear to do it on purpose (see my post on extracting passwords from Plesk)
Most developers then settle on a function such as md5() or sha1() to hash the password, and to store it in a sort-of encrypted fashion. A hash is often called one-way encryption. That is, you can take a password (or pretty much any data), pass it through a hash algorithm, and out the other end will come some seemingly random characters that cannot be decrypted.
So, if you're storing passwords in a way that cannot be decrypted, how can you check that the passwords match?
That's easy! You take the stored hash, you then take the entered password, hash it using the same algorithm, and then you compare the two values. If they match then you have entered a valid password (probably), and if they don't then you haven't (definitely).
So, we've already established that most developers settle on using md5() or sha1() to store their passwords. This is generally frowned upon by the security world. I don't want to go in to why right now, as that's a massive article in itself, but the defacto article on the subject can be found here: How To Safely Store A Password
Fast forward to June 2013, and PHP 5.5.0 is released. With it comes four new password hashing functions:
PHP 5.5 Password Functions
password_hash() uses a cryptographically strong algorithm to hash your password. The default is BCrypt, or Blowfish. If you read the article on codahale.com above then you should recognise this as one of the highly recommended password hashing algorithms. The real beauty of this function for me however is that if you don't specify a hash algorithm, that the one that is currently considered the most secure will be automatically applied. So you can just use password_hash('my password') and you will always have the recommended hash algorithm in use. Brilliant.
password_verify() accepts two arguments: A hashed version of a password, and a plain text version of the password. It will then return TRUE or FALSE, depending on whether the hash matches or not. It's that simple.
But how does it know which algorithm to use? The hash itself has sufficient information in it for the function to understand which algorithm was used to hash it. It can also establish the salt. It's all very clever black magic, smoke and mirrors. Suffice to say that if you encrypt a password with Bcrypt today, and you're using a different algorithm 10 years down the line, this method should still be able to decrypt your old stored passwords anyway.
Something we haven't touched on yet is the ability to change the default algorithm, or the ability to increase the computational power required to hash the password to (theoretically) make it harder to crack. If you decided that after 2 years that you need to change the algorithm, or to increase the processing cost, what effect will that have on your existing passwords? Well the password_verify() function will continue to work as it ever did, but wouldn't it be great if you were able to easily detect that a password is hashed using an old configuration, so that you can rehash it using the latest settings and then update your user table? Yes, yes it would. And password_needs_rehash() is exactly how you do it.
password_needs_rehash() simply takes the hash value and the algorithm settings as arguments. It then returns TRUE or FALSE, depending on whether your password needs to be rehashed or not (ie TRUE = rehash, FALSE = carry on as you were).
The last function in this suite is password_info(). It takes just the one argument, and that is the hash itself. From this it will return back an associative array that details the hash ID, hash name and the cost value. That is all.
How to use the PHP 5.5 password functions in PHP 5.3 or PHP 5.4
I wrote a small library many years ago to handle exactly this, but the fact is that there is a compatibility layer available from Anthony Ferrara which is much better. Have a wander over to Github to take a look, or go get it via Composer