Hash Storage: Make Attackers Work

So you hash your passwords? Good. Do you salt? That’s good. Do you use a strong hashing algorithm (PBKDF2/bcrypt/scrypt)? Great! But how do you store the hashes? What happens when you get hit with a SQL injection attack?

I’m a big believer in defense in-depth – not that marketing garbage about stacking layers of blinky-light boxes, but using techniques to add extra work for an attacker. You might not be able to stop every attack, but the more work they have to do, the better the odds they won’t get everything they want.

So, let me describe a scenario:

$company stores passwords as SHA1(password + salt) hashes (terrible, I know – but still common). They install a new web application that connects to their main database – this new application has a previously undisclosed flaw, SQL injection.

$evil_hacker finds this flaw and looks for companies that use the application; finds $company, exploits, dumps their user table and posts the hashes on PasteBin. Having both the hash and salt, those hashes will fall quickly.

We’ve all seen it before – far too many times.

Now, let me modify the scenario slightly: instead of SHA1(password + salt), make it AES128(salt : SHA1(password + salt), key) – where key is an application secret.

Now, we have a different story – as long as the application secret remains secret, the hashes are safe. Stored in the clear, they will fall quickly – encrypted, they are safe as long as the secret remains safe.

In the case of many SQL injection attacks, the attacker is able to access the data, but doesn’t have access to the application source code. In these cases, you can add a few extra milliseconds to processing time and as a result stop attackers from using stolen data from an entire class of attacks. There’s nothing new about this idea – but I don’t see it used nearly often enough.

Obviously this doesn’t help if the attacker is able to retrieve the source code, so you still need to use a strong hashing algorithm. Encrypting a strong hash buys you extra protection against certain attacks, and maintains a strong baseline against others – at a very small performance cost.

In case of a breach, I would still assume that the attacker knows the encryption key, and reset passwords accordingly. Seeing as it takes time to find out the extent of the breach, it’s safest to act as if the attacker has everything and limit the damage as quickly as possible. In reality though, the odds of the passwords being exposed as a result of a SQL injection attack is reduced significantly thanks to the added encryption layer.

Storing the data like this keeps everything secure, and in a single field (and makes it easy to integrate into existing applications):

<IV>:AES128(<salt>:HASH(<password><salt>))

This way all of the material needed is encrypted; though it could be argued that just encrypting the hash or the salt would be sufficient (assuming the salt is at least 16 bytes and generated by a CSPRNG). The core idea here is to deny the attacker of as much information as possible; making it as difficult as possible to find passwords.

There are no silver bullets, but we can take advantage of the tools we have to make attackers work harder to get our secrets.