« Let’s Talk About Facades | New Object Oriented PHP Training Opportunities » |
Any web developer writing PHP applications is eventually going to have to store user passwords. Most developers have at least learned that storing a password in plain-text isn’t secure, but for many of us, we still use insecure methods.
The problem is, that if our web application is ever hacked, insecure password hashing algorithms will make it simple, easy and quick for attackers to break our passwords.
For those developers who understand they can’t simply store the password in plain text, they typically employ one of two mechanisms. Both are insecure.
The first mechanism is using some type of insecure hashing algorithm, like MD5 or SHA1. These “fingerprint hashes” produce the same hash for a given string of characters, making them ideal for identifying files and other items, but are not cryptographically strong enough for hashing passwords. Even if you add a salt, your passwords will be inherently insecure especially since these algorithms require that the salt be stored alongside the password for two reasons: first, you are likely to screw up the randomness of the salt (weakening the encryption). Second, MD5 and SHA1 are meant to be fast hash algorithms, meaning that a determined attacker can brute force their way through your passwords with ease.
<?php $hashed = insecure_hashing_function($salt . $password); # This is BAD
Another potential failure of security for developers is somehow encrypting passwords with a key, so they can be decrypted for various purposes. I came across the following code sample in a project I was auditing:
So, if “traditional” methods aren’t enough, what can a developer who cares about security do? The answer lies in a feature introduced as of PHP 5.5: a native password hashing API for PHP.
This library contains two functions that will make it easier for you to hash passwords: password_hash() and password_verify(). These functions essentially wrap built-in PHP behaviors for cryptography, but build in sane defaults that will serve even the most inexperienced developer well.
<?php $password = 'SuperS3cr3+P@ssw0rd'; $hash = password_hash($password, PASSWORD_DEFAULT); # $2y$10$PwbhVaqzuUSl5wSdi.iCK.RBz5pQp/a.6lRdSRttY8s2qs7dn5hTq $verified = password_verify($password, $hash); # true
Astute developers who run the above code will recognize that the hash I give isn’t repeated by their own password_hash function. That’s by design: each password hash should be unique, even for the same password between different users! Also, the second argument lets developers pick the encryption algorithm they want to use; PHP 5.5 defaults to bcrypt, which is usually sufficient.
Ah, that is a problem, isn’t it? PHP 5.5’s adoption rate is fairly low right now. But you’re in luck: Anthony Ferrara has backported the native password library for earlier versions of PHP. You can check out his work on GitHub. The library requires PHP 5.3.7 and later, which is well-distributed.
The bottom line is that there’s no reason for you to be hashing passwords yourself. You should be relying on a well-established methodology that will help keep your users secure in the event of a data breach.
Brandon Savage is the author of Mastering Object Oriented PHP and Practical Design Patterns in PHP
Posted on 2/26/2014 at 12:03 pm
Davi Marcondes Moreira (@devdrops) wrote at 2/26/2014 12:22 pm:
Hi!
Thanks for the info, that’s an important message! I’d like to know your thoughts about the use of bcrypt to perform the password hashing when you’re still using PHP 5.3 (for example, using framework resources like Zend\Crypt\Password\Bcrypt).
Brandon Savage (@brandonsavage) wrote at 2/26/2014 12:25 pm:
I would consider using a Zend library to be acceptable, just make sure that you’re properly salting and doing the hash correctly. See this from Anthony Ferarra on password security and bcrypt: http://blog.ircmaxell.com/2012/12/seven-ways-to-screw-up-bcrypt.html
Joel Clermont (@jclermont) wrote at 2/26/2014 6:55 pm:
I agree with your advice to use password_hash, but I want to call out one thing:
“Even if you add a salt, your passwords will be inherently insecure, especially since these algorithms require that the salt be stored alongside the password.”
Storing the salt with the password does not weaken security. The password_hash function does the exact same thing. It has to. If the salt isn’t stored, you could never verify the password’s validity in the future.
The main advantage with letting password_hash generate your salt is that it will do a much better job generating a truly random salt as compared to your average developer.
Brandon Savage (@brandonsavage) wrote at 2/26/2014 6:56 pm:
Thanks. You’re absolutely right. I’ll make an update to the post.
Pádraic Brady (@padraicb) wrote at 2/27/2014 5:03 am:
I see someone else caught the salt issue. Yes, storing the salt alongside the password weakens nothing. The point of a salt is to make rainbow tables useless and force an attacker to brute each password separately. You should note that a single global salt is actually a weakness – so you weren’t that far off the mark ;). Global salts were pretty popular once upon a time.
No description of bcrypt would be complete without mentioning the cost option for bcrypt in password_hash(). It defaults to 10, but it should be adjusted depending on your hardware. It should not be a surprise when PHP increases the default with the passage of the years to reflect Moores Law – the target is for a hash to take 0.2-0.5 seconds.
orciny wrote at 2/27/2014 6:14 am:
Does the password_hash function produce portable hashes? Some password hashing libraries produce non-portable passwords by default but you can create less-secure portable ones by setting a flag. I assume that this is due to using OS level crypto functionality to generate the hashes.
Brandon Savage (@brandonsavage) wrote at 2/27/2014 9:00 am:
I tested a password hash on >1 machine, and I was able to verify a hash generated on another computer. I believe these hashes are portable.
Edmar wrote at 2/28/2014 6:45 pm:
Brandon, sorry, but “I believe these hashes are portable” is just not enough for someone advocating the use of this API.
Security is paramount, of course, but so is stability (over time) and portability of password storage.
Personally, I would not commit user password hashing to a PHP-only solution.
Mark (@mogosselin) wrote at 3/20/2014 9:20 pm:
Thank you for that post Brandon! It’s simple and easy to understand.
It’s always like that: the best security code you could use is probably NOT something you wrote yourself! (Except if you’re a security specialist).
That’s a good thing that PHP included something out of the box to store secure password. I always cringe when a customer sends me a database with “md5-ed” passwords (or even clear text sometimes). At least, even if the developers don’t quite understand what they’re doing when using the password_hash() and password_verify() methods, it will still be easier than writing their own md5 methods, so more secure.
timoh wrote at 3/24/2014 8:22 am:
I’m sorry my reply comes a bit late, but I’d like to comment Paddy’s suggestion concerning the 0.2-0.5 seconds hashing time.
> No description of bcrypt would be complete without mentioning the cost option for bcrypt in password_hash(). It defaults to 10, but it should be adjusted depending on your hardware. It should not be a surprise when PHP increases the default with the passage of the years to reflect Moores Law – the target is for a hash to take 0.2-0.5 seconds.
The target hashing time, 0.2-0.5 seconds, as mentioned by @padraicb is arguably wrong (for interactive login scenarios). The security/crypto community talks about ≤ 100 ms hashing times for interactive logins (i.e. Colin Pervival’s scrypt paper).
I wrote something about this a while back: http://timoh6.github.io/2013/11/26/Aggressive-password-stretching.html
Claude Adrian (@CodeAngry) wrote at 3/24/2014 10:36 am:
@padraicb and @Edmar make a good point. A proper password hash needs to be portable.
I always test crypto and hashing in C++ and PHP as I need web-services written in C++ to use the login data stored by the PHP frontend. You can even write a PHP extension to implement the same C++ hashing algo in PHP if it does not already exist.
« Let’s Talk About Facades | New Object Oriented PHP Training Opportunities » |