Passwords. Are you doing it right?Thunder Raven-Stoker
Mar 30th, 2015
With Mashable reporting this week that Uber logins can be bought online for $1 it's high time to check how you handle passwords in your PHP app.
The idea of usernames and passwords being available to buy online, for just one dollar no less, is pretty alarming to say the least. It should be noted though that Uber themselves state that they haven't been hacked and are instead pointing the finger at Amazon's breach.
We can't do much about our users using the same login credentials on every web site. But we can make sure that we're storing them correctly in our applications.
TL;DR: If you're not using the password_hash() function in PHP, you're doing it wrong. Scoot to the bottom of this article for links.
Let me ask you something. Would you be confident in posting your password mangling code up on a web site for all to see?
What if I told you that you should be?
One of the most common mistakes that I've encountered over the years is the idea that implementing your own super secret, super convoluted encryption/hashing/mangling routines is somehow the safest route to take.
Well, it isn't.
There are two main reasons as to why this is so.
Firstly, unless you're the world's number one expert on cryptography then there are other people much smarter than you in this matter. That's the approach that I take and I think it holds true. The world is full of very smart cryptography people who are better at that game than I am. In this regard, I'm happy to defer to their superior knowledge.
The second reason why your own super convoluted algorithm isn't as safe as you think it is is because it may already be public knowledge whether you've blogged it or not.
Like it or not, your code could still have been leaked
"But my code is safe. It's not in the webservers doc root and even if it was, it's in php so only gets parsed by the server not sent to the browser."
Not quite. The most common mistake that I've encountered in this realm is the idea that the only way for knowledge of your password handling to escape your control is through an attack on your servers from the outside.
Whilst still a very credible threat, it's much less likely than some of the alternative ways in which your code can leak.
Let me give you a couple of scenarios, both firmly fixed in truth as they're both drawn directly from experience.
Scenario 1: You have a super eager developer on your team who decides he's going to take his work home with him for the weekend. One Friday afternoon, he checks out the repo onto a USB drive and supplements it with a dump of the database. However, Friday evening involves rather a little too much time in the pub and as a result, our beloved super eager developer manages to lose the USB drive somewhere between the kebab shop and the night bus home. Result? All of your code and all of your database is now in the public domain.
Disclaimer: When one of my teams did that, the database dump was at least dummy data. Still...
Scenario 2: One of your developers goes for a job interview at another company. Yes, I know they said they had a dentist's appointment but it does happen! Anyway, during the interview the candidate (your developer) is asked the question:
"What's your current method of handling passwords? Do you encrypt them? Do you hash them?"
At which point the candidate goes on to describe how passwords are currently handled at your company. This does happen because I've asked those questions myself and gotten rather detailed answers in return. Job candidates tend to be keen to impress potential employers during interviews, which makes getting this sort of info easier than it might ordinarily be.
These two scenarios are absolutely genuine but unlikely to be the only two ways that knowledge of your password handling algorithm can leak out of your company. For starters, if you're not hosting your repositories internally, then all of the employees at your hosted repo service (github? bitbucket?) can read your code too.
Let's not forget the most obvious location where your code is no longer solely under your own control: on the web servers themselves. Do you use a hosting company for your web site? A cloud based service? Do these companies have, you know, employees? Now I'm not saying that these guys spend their days peeking at everyones code. What I am saying is that if you can't absolutely guarantee the secrecy of the code, you can't use the term secret. Your super secret, super convoluted hashing algorithm then just becomes a super convoluted algorithm.
It follows then that you should have the confidence to be able to blog about how you handle passwords. It's entirely possible that the knowledge is already out there.
You might recognise this thinking as being along the lines of Kerckhoff's work on military ciphers. The second of his design principles states that "[an encryption system] should not require secrecy, and it should not be a problem if it falls into enemy hands". Formulated in 1883, it still holds true today.
Please do note however that I'm not for a moment suggesting that you do actually go and blog it, only that you should have enough confidence in what you do with passwords for blogging about it to not matter. Again, the only safe assumption is that your password handling algorithm is already public knowledge. (This in itself is Shannon's Maxim: "The enemy knows the system.")
What do I do with passwords?
So in the same spirit, here's mine
$hashedPassword = password_hash( $password, PASSWORD_BCRYPT, array('cost' => 12) );
At this point in time, using PHP's native
password_hash() function with the bcrypt algorithm is the strongest approach to hashing a password short of using a specialist package. There are a couple of things to note in that code snippet above though.
Firstly, I don't supply a salt. The
password_hash() function will generate its own for you and this is the recommended approach. You should never use your own salt. If you do, it's just as vulnerable as the rest of your code and will only provide you with a very false sense of security. Please do note that that applies equally to dynamic salts as it does to static ones. You simply should not provide your own salt; custom, dynamic, static or otherwise.
Secondly comes the most important part. The third parameter to the
password_hash() function is an array of options. Here I've only used the one: cost. The cost option here tells the
password_hash() function how expensive to make the hashing algorithm. The guideline here is that the process should be as expensive as you can possibly make it without making a significant impact on the application's performance or the user's experience. Consequently, the generally accepted benchmark seems to be that the hashing process should take around 250 milliseconds.
This is where you get to tweak around with the cost value, since the 250 millisecond mark will mean providing different cost values for differently powered servers. If your web servers are bursting at the seams with more processors than NASA, you'll need a higher cost value to reach that 250ms. If however, you're running on more regular fare, you'll adjust the cost down.
The PHP manual provides a handy code snippet for benchmarking and finding the right value for your gear - the link is below.
There's only one constant in this game though: the bad guys have much more computing power at their disposal than you do. Even if your servers and theirs were evenly matched, you'd still be asking them to spend 250ms for every brute force crack attempt at every password. For just one character of the password there are 95 possibilities, and that's only from using the regular ASCII character set. Using the password_hash() function's dynamically generated salt for every password also means that generating a rainbow table now becomes unfeasible.
When the process for computing a password's hash is sufficiently expensive, then the advantage is with you. Your users will remember (of course they will!) the correct sequence of characters to enter into that login form. End result? You only need to expend those 250ms once in order to verify the user's input.
This then is our current state of play. For sure, it's likely to change in the future as it's a constant game of cat and mouse. But for now, you should be using either PHP's native password_hash() function, or, if you're hosting on an older version than PHP5.5, you can either upgrade (preferred!) or use Anthony Ferrara's excellent compatibility library. If your PHP is sub 5.3.7, you really must upgrade!
There's still no excuse for coming up with your own algorithm though. Not unless you really are the world's foremost authority on encryption and hashing.
And don't even think of supplying your own salt. Let password_hash() do it for you.
Thank you for reading. I hope you found this article entertaining, stimulating and helpful. If you think it might be useful to others, please do consider sharing a link to it in your blog or on social media. There are some buttons below to make this easy.