Storing User Passwords

Best practices for handling and storing user passwords.

Storing User Passwords

Yesterday we announced that our servers had been compromised by μηc1ePennλβαg$ who had tampered with all our images and replaced them with various versions of Monopoly. That was an April Fools ^^

In this post, I'll attempt to explain how KallaX.io (and most websites) securely handles passwords. This article is not meant as a guide but as a high-level explanation for people who might find it interesting.

Hashing is not Encryption

A hash function deterministically converts a variable-sized value (e.g. a password) into a value of fixed size. The process can not be reversed. This means that it is easy to calculate the hash when you have a password - but it is not possible to calculate the password if you only know the hash value.

MyPassword42 becomes 656229CA803267CF1786500BFDAABEBB
myPassword42 becomes CD5B999AE2FC2BA124A455213B3A919B

Instead of comparing the entered password directly, we calculate the hash value and then compare it to the hash value we have stored. This way we can not accidentally leak or lose your password because we don't even know it!

Encryption is different in that it is a two-way function. Given a value and a key you can encrypt and get a cipher. Given the cipher (encrypted value) and a key, you can decrypt it again and obtain the original message.

MyPassword42 encrypted becomes T5Whzz3vykb9
T5Whzz3vykb9 decrypted becomes MyPassword42

Storing encrypted passwords is not safe as it would require us to have the key somewhere on our server – and if you have something you might risk losing it.

Validating a Password

Now we can start comparing methods for password validation. The most primitive is A – the client sends the password and the server checks if it is equal to the password stored in the database. This should never happen!

B is a better model and an 'ok' approach. The client sends the real password but the server throws it away as soon as it has calculated the hash and compared it with the one stored in the database. It is quite common to transmit the real password which is okay on a secured connection – it is often done to perform various checks on the password (e.g. length requirements). However, the password might leak into server logs even if you are Twitter or GitHub.

Simplified version of how passwords are transmitted between services.

The model we have opted for on KallaX is model C. We hash the password once directly on the client, in your browser - running locally on your machine, so the server never sees your password even once.

Your hashed password is hashed (again) and salted 1000 times using SHA256 before being stored in our database. Salting is a technique where you add a (pseudo) random value to the password before hashing it.

MyPassword42 becomes 656229CA803267CF1786500BFDAABEBB
MyPassword42+A becomes 2E37477257CC3A69008674604F2EB0A6
MyPassword42+B becomes 3A53158B86E4C3FA7C0B2E99F90F79B0

The reason is that the same password will always hash to the same value – this is great for when we need to compare, but less great if someone suddenly has access to our database. They will not be able to tell what the password is, but they will be able to see which users (likely) use the same password!

So we pick a unique value for each password and append it to the password before hashing it. We store the value (salt) we picked in the database so we can reproduce the process when we need to perform password checks on login.

MyPassword42+A becomes 2E37477257CC3A69008674604F2EB0A6
2E37477257CC3A69008674604F2EB0A6+A becomes 96E0993E...
96E0993E386FA569CA28E9DA21CC7D8D+A becomes 75B5EE29...
... + 998 times more

Cool. Cool. Why 1000 times though?
It gets a bit complicated – but assume someone had access to the database and knew the password for one account. If we did not salt – then they could simply look for accounts where the password hashed to the same value and instantly know that the passwords are likely to be the same.

If we salt once then they need to run this salting and hashing step for every single password in the database, which they can because they have the salt, but it would take some time – a few seconds probably.

Whenever we salt one more time – the potential malicious person also has to perform that calculation one more time but for all the passwords! We are basically trying to force them to spend so much time cracking the passwords that they can't (that what most of cyber security boils down to...)

We could potentially salt it a million times but that would also requires us to do a quite a significant amount of work every time we wanted to check if the entered password is correct – resulting in a very loading sequence and high computation bills. So, 1000 is a decent compromise between security and cost.

Conclusion

The way we store passwords is industry standard and considered safe. Hashing once on the client is less common. The downside to client hashing is that we can not enforce password requirements (server-side).

These are implemented client-side only which means a technical user can pick a non-secure password, hash it, and send it to us. If you want to go through that much trouble just to have a bad password, be my guest.

However, by hashing on the client-side our server never see the real password and we thus can not accidentially leak it. You can have direct access to our servers memory and would still not be able to get a single password.

We also efficiently prevent long-password denial of service attacks which is a primitive attack where you choose an extremely long password and thus force the server to spend a long time calculating the hash (in the hopes of halting or crashing the server). It is easy to prevent with a max password length too ^^

Notice we store the passwords in the format:
<hashed_password>|<salt>|<algorithm>|<iterations>

This allows us to upgrade to a more secure algorithm – or bump the number of iterations at a later stage if needed without having to force every user to create a new password. I highly recommend storing passwords in a similar format.

Note I use MD5 hashing throughout the article. This is only because the hashes are shorter and thus a bit more readable. Never use it for hashing password - it is not considered secure. We use SHA256 on KallaX.