During the past week, I had an idea on how to easily red flag potential employers: ask to see one row of their user table, and look at the password field. If the password is in plaintext, then run for the door.
When companies such as Blizzard are suffering from authentication system hacks, and appear to be vulnerable to dictionary-style attacks, then there exists a problem that is endemic across the industry.
As IT professionals, I would expect that many of us have come across registration and log-in systems that make Swiss cheese look impenetrable by comparison.
The sad reality is that this is easily fixable.
Passwords protect your users, not you
The reasons for storing passwords as hashes is not to prevent your system from being cracked; it’s to protect the users in the event of a breach. Because users are human, and therefore lazy, they will reuse passwords over and over again. A user’s online and offline identity should not be threatened because your systems failed to be secured properly.
Therefore, in the case of a breach, a user’s password should be protected sufficiently to deter the hacker from even attempting to brute force a password or turning to dictionary attacks or rainbow tables.
Throw out the md5/sha1 and fetch the salt
If you are currently using md5 or sha1, the best way to demonstrate that you are vulnerable is to head over to crackstation.net and see how easy it is to crack md5, sha1, and other hashing methods with lookup tables.
Even if you use strong passphrasing with tens of characters, and increase the keyspace by using mixed case and symbols, some of your users will be using weak passwords. And they will be the first to go when the dictionary attack is used.
The first line of defence is to inject a salt into the stored password. Using a salt provides a way to make passwords longer, and adds some randomisation to weak passwords.
The one caveat is that the salt needs to be unique for each user — it does no good to hard code a standard salt into your source code and expect that to be enough.
In the case of a hack, it’s reasonable to assume that if your database has been compromised, then it is likely that your source code has been compromised as well. With such a situation, one should expect that any hardcoded salts or series of hashes on hashes, such as sha1(md5($password)), would be discovered, and allow a dictionary to be quickly built to attack your database.
Even in the case of a unique salt for each user, a hacker may still build a bespoke dictionary or rainbow table to decode a password for individuals that are high profile and/or profitable enough. This becomes feasible when computing power has reached the point where hundreds of millions of md5 hashes can be computed per second.
What is needed is a method to deter a hacker from even attempting to attack an individual user in the first place.
Enter the Blowfish
There exists a multitude of solutions that each have their merits, but for this article we will be focusing on the Blowfish algorithm developed by Bruce Schneier. The primary reason is that there is a native implementation for Blowfish in PHP 5.3.7 and greater, and library implementations available for PHP 3.0.18 and above.
To make use of Blowfish in PHP, we need to invoke the crypt function. The salt for Blowfish takes the form of [algorithm][cost]$[22 digits from “./0-9A-Za-z” as salt], where algorithm is “$2y$” and cost is a base-two logarithm, which determines how many iterations to do between 2^4 to 2^31 (16 to 2147483648). A full example would be a salt of “$2y$14$wHhBmAgOMZEld9iJtV./aq”.
The cost factor is an important part of Blowfish, and is intended to increase the computation power required to brute force the password. In our example above, we are doing 16,384 (2^14) iterations of Blowfish before producing the result. Increment the cost factor into the high twenties to see the time needed to calculate a single hash.
To put it into practice, the following line:
crypt("testbahbah", "$2y$14$wHhBmAgOMZEld9iJtV./aq");
gives us a resultant hash of
$2y$14$wHhBmAgOMZEld9iJtV./aeunF8UBxc5UA0AqDnqZ0MQ1ivv2Y0SUG
What makes this extremely useful is that the first 28 characters of the hash contains the salt that we used prior, so we don’t need to store it separately. This means that we can later check the validity of the hash by using our result as the salt.
Thus, if we were to test the crypt code used above, we would use:
if(crypt($password_to_check, $password_hash) == $password_hash) {
//valid password
}
You may realise that “$2y$14$wHhBmAgOMZEld9iJtV./aq” is not an exact match to “$2y$14$wHhBmAgOMZEld9iJtV./ae”. This happens because Blowfish uses a 128-bit hash, but 28 characters of hex is 132 bits. The way this is resolved is that the last four bits are dropped, thus the change in the last character that is shared between the salt and the hash. In fact, any extraneous bits in the salt are dropped, so crypt($password_to_check, $password_hash) is equally as valid as crypt($password_to_check, $password_hash.”011111000″).
If you are looking across the internet for a Blowfish implementation, do make sure to check the way the salt is generated. I’ve seen far too many posts using a form of sha or md5 hashing to generate the salt. This results in cutting the salt language from 63 possible characters down to 16.
A simple way to generate the salt would be:
$salt = "$2y$14$";
for ($i = 0; $i < 22; $i++) {
$salt .= substr("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", mt_rand(0, 63), 1);
}
These few code snippets should be enough to start you on your way to implementing a Blowfish authentication system. Don’t be worried about storing the full hash in a database; that’s how it’s meant to be.
A small step
While Blowfish is a nice starting point, it’s not the endgame in protection.
There are many better algorithms available in PHP’s mcrypt extension. They involve a tad more work and understanding than Blowfish, but if you truly want to get serious about password protection, and take encryption farther than just password hashing, it’s well worth a read.