At this point, I think it’s a relatively well known fact that passwords should be hashed in storage. If you or your development teams are storing passwords in plain text, the keys to every user’s kingdom are sitting there begging to be stolen. Really, all it takes is one little SQL injection to expose every user’s password (SQLMap is a wonderful tool, but we’ll get to that later). Sony had a terrible year in 2011, suffering breach after breach. One particular breach was caused by a SQL injection in their Sony Pictures website, which led to the exposure of tens of thousands of user accounts across multiple systems. The kicker: all passwords were stored in plain text. World Poker Tour and Yahoo Voices also had the same type of breach in 2014 and 2012 respectively.
You might be sitting there thinking, “Sweet Mother of God… They’re coming for me next!” but don’t worry, we’ll build up your defenses together. There are multiple steps that can be taken to ensure that your users’ passwords are stored securely. I’m about to throw a butt load of information at you, so fasten those seat belts.
Hashing With A Salt
The first step to protecting passwords in storage is choosing a strong hashing algorithm. For those who are unsure of what hashing is, it is a one-way algorithm that takes a plain text string and outputs a fixed-length value that represents the original string. More simply put, it computes a value (the hash) of the original string (the password) that cannot be un-hashed. Examples of strong hash functions are SHA-256, SHA-512, RipeMD, and WHIRLPOOL.
This is just the first step to secure passwords. An attacker can break hashes relatively quickly using look up tables or rainbow tables, but adding a salt can prevent this. A salt is a random value that is appended to the password before it is hashed. The salt prevents the attack from using pre-computed tools if attempting to crack passwords, because the salt adds a random factor to the hashing. A salt should be unique to each user’s password (never re-use a salt), as well as 32 bytes (or more) in length. Most languages have a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG) built in to one of its libraries. Salts can be generated securely using these CSPRNGs and stored in the user account table along side the password hash.
Make Those Hackers Work For It
Now that we’ve covered the basics, let’s really lock down those passwords. Using a strong hashing function and a salt will protect you from specialized attacks such as look up tables and rainbow tables, but it doesn’t protect against dictionary/brute forcing attacks. Adaptive hashing functions (such as Bcrypt, PBKDF2, or Scrypt) are quickly becoming the standard for password protection. The idea is simple: make computing the hash computationally expensive. In an experiment done by Jeremi Gosney using a 25 GPU cluster, the numbers were as follows:
Yes, you read that right, 63 BILLION password guessing attempts per second against passwords hashed using SHA-1. Granted, this is using a highly advanced, specially developed machine dedicated to cracking passwords. But it shows just how powerful an adaptive hashing function can be. Brute forcing attacks become to slow to be worthwhile. This is achieved by using key stretching in the hash function. PBKDF2 and Bcrypt are standard algorithms that utilize this method.
Scrpyt is a similar function, but addresses one flaw. PBKDF2 and Bcrypt both make the computation of the hash expensive, but are very parallel. If you have twice the hardware, you can break twice the passwords. Scrpyt addresses this by not only consuming CPU, but memory as well. It will attempt to eat as much memory as possible while completing the calculation, the opposite of what all good programmers want their code to do.
Breaking Keyed Hashes? Impossibru!
Even with all of these protection measures in place, the attacker still has a hash that can be used to validate whether or not a password is correct. By introducing a secret key, only those who have the key can use the hash to validate a password. This can be accomplished by:
- Encrypting the hash using a strong cipher, such as AES-128
- Including a secret key in the hash using a keyed hash algorithm like HMAC
This key has to be protected from an attacker, even in the event of a full data breach. The key must be stored in an external system, such as a physically separate server dedicated to password validation. If you can’t afford multiple dedicated servers or hardware devices, it is still possible to get the benefit of these techniques. If we look back to the data breach examples we saw before, most systems are breached using SQL injection. SQL injections do not give attacks access to the local file system. If you generate a random key and store it in a file that isn’t accessible from the web, and include it into the salted hashes, then the hashes won’t be vulnerable if your database is breached using a simple SQL injection attack.
- Use a strong hashing algorithm
- SHA-256, SHA-512, RipeMD, WHIRLPOOL
- Use a salt
- At least 32 bytes
- Unique per user, per password
- Use an adaptive hashing function
- Bcrypt, Scrypt, PBKDF2
- Use a secret key
- Encrypt hashes using AES-128
- Use a secret key in the hash (HMAC)