Securing user password

Prem Kumar Singh
Deskera Engineering
6 min readMay 11, 2020

--

Storing user passwords is a critical component of any web application. When you store a user’s password, you must ensure that you have it secured in such a way that if your data is compromised, you don’t expose your user’s password.

There have been many high profile cases of websites and web applications that have had their user details compromised. This is made even worse when the developers of the website have not stored the user’s password in a secure way.

We at Deskera build a highly secured infrastructure to secure our user’s password in our IAM database. I am not going to talk about how we have built our system but I am going to highlight common good practices to store secure passwords while building software to safeguard from hackers.

Hashing passwords

What is password hashing?

“Hashing” passwords is the common approach to storing passwords securely. A “Hash” is a one-way function that generates a representation of the password. So when a user signs up for an account and they choose a password, the password is stored as the generated hash, rather than the actual characters that the user typed in. When you run a password through a hashing function, it will always produce the same output. When the user tries to log in with their email and password, the entered password is hashed again and then compared to what is stored in the database. If the two hashes are the same, the user has entered the correct password.

Hashing a password is good because it is quick and it is easy to store. Instead of storing the user’s password as plain text, which is open for anyone to read, it is stored as a hash which is impossible for a human to read. Below are the samples. Hash is trimmed to fit in a single line.

hash(“hello”) = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e…hash(“hbllo”) = 58756879c05c68dfac9866712fad6a93f8146f337a69a…hash(“waltz”) = c0e81794384491161f1777c232bc6bd9ec38f616560b120…

If you are thinking of writing your own password hashing code, please don’t!. It’s too easy to screw up. DO NOT WRITE YOUR OWN CRYPTO! The problem of storing passwords has already been solved.

Well-known and commonly-used hashing algorithms are MD5, SHA-1, SHA-2, and SHA-3.

How Hashes are Cracked?

Dictionary Attack: A dictionary attack uses a file containing words, phrases, common passwords, and other strings that are likely to be used as a password. Each word in the file is hashed, and its hash is compared to the password hash. If they match, that word is the password. These dictionary files are constructed by extracting words from large bodies of text, and even from real databases of passwords. Further processing is often applied to dictionary files, such as replacing words with their “leet speak” equivalents (“hello” becomes “h3110”), to make them more effective.

Brute Force Attack: A brute-force attack tries every possible combination of characters up to a given length. These attacks are very computationally expensive and are usually the least efficient in terms of hashes cracked per processor time, but they will always eventually find the password. Passwords should be long enough that searching through all possible character strings to find it will take too long to be worthwhile.

Lookup table: Lookup tables are an extremely effective method for cracking many hashes of the same type very quickly. The general idea is to pre-compute the hashes of the passwords in a password dictionary and store them, and their corresponding password, in a lookup table data structure. A good implementation of a lookup table can process hundreds of hash lookups per second, even when they contain many billions of hashes.

Reverse lookup table: This attack allows an attacker to apply a dictionary or brute-force attack to many hashes at the same time, without having to pre-compute a lookup table. First, the attacker creates a lookup table that maps each password hash from the compromised user account database to a list of users who had that hash. The attacker then hashes each password guess and uses the lookup table to get a list of users whose password was the attacker’s guess. This attack is especially effective because it is common for many users to have the same password.

Rainbow Tables: Rainbow tables are a time-memory trade-off technique. They are like lookup tables, except that they sacrifice hash cracking speed to make the lookup tables smaller. Because they are smaller, the solutions to more hashes can be stored in the same amount of space, making them more effective. Rainbow tables that can crack any md5 hash of a password up to 8 characters long.

Let’s discuss the solution to make it impossible to use lookup tables and rainbow tables to crack a hash in the next section.

Adding SALT

Lookup tables and rainbow tables only work because each password is hashed the exact same way. If two users have the same password, they’ll have the same password hashes. We can prevent these attacks by randomizing each hash so that when the same password is hashed twice, the hashes are not the same as shown below.

hash(“hello”) = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824hash(“hello” + “QxLUF1bgIAdeQX”) = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1hash(“hello” + “bv5PehSMfV11Cd”) = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab

Just by randomizing the hashes, lookup tables, reverse lookup tables, and rainbow tables become ineffective. An attacker won’t know in advance what the salt will be, so they can’t pre-compute a lookup table or rainbow table. If each user’s password is hashed with a different salt, the reverse lookup table attack won’t work either.

A salt is also known as a nonce, which is short for “number used once.” We can randomize the hashes by appending or prepending a random string, called a salt, to the password before hashing. A new random salt must be generated each time a user creates an account or changes their password.

To make it impossible for an attacker to create a lookup table for every possible salt, the salt must be long. A good rule of thumb is to use a salt that is the same size as the output of the hash function. For example, the output of SHA256 is 256 bits (32 bytes), so the salt should be at least 32 random bytes.

How do we generate the salt, and how do we apply it to the password?

Salt should be generated using a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG). CSPRNGs are designed to be cryptographically secure, meaning they provide a high level of randomness and are completely unpredictable. CSPRNGs exist in almost all popular programming platforms. EX: In Java, we can use java.security.SecureRandom and in PHP we can use mcrypt_create_iv, openssl_random_pseudo_bytes

To Store a Password

Generate a long random salt using a CSPRNG.

Prepend the salt to the password and hash it with a standard password hashing function like PBKDF2, bcrypt or scrypt.

Save both the salt and the hash in the user’s database record.

To Validate a Password

Retrieve the user’s salt and hash from the database.

Prepend the salt to the given password and hash it using the same hash function.

Compare the hash of the given password with the hash from the database. If they match, the password is correct. Otherwise, the password is incorrect

Making Password Cracking Harder: Slow Hash Functions

Salt ensures that attackers can’t use specialized attacks like lookup tables and rainbow tables to crack large collections of hashes quickly, but it doesn’t prevent them from running a dictionary or brute-force attacks on each hash individually. To make these attacks less effective, we can use a technique known as key stretching.

The idea is to make the hash function slow, so that even with a fast GPU or custom hardware, dictionary and brute-force attacks are too slow to be worthwhile. Select your iteration count based on the concurrent load on the authentication server so that this slowness will not be noticed by the end-user.

Key stretching is implemented using a special type of CPU-intensive hash function. Don’t try to invent your own–simply iteratively hashing the hash of the password isn’t enough as it can be parallelized in hardware and executed as fast as a normal hash. Use a standard algorithm like PBKDF2 or bcrypt.

Recap

The salt needs to be unique per-user per-password. Every time a user creates an account or changes their password, the password should be hashed using a new random salt. Use a strong random number generator to create a salt of 16 bytes or longer. Never reuse a salt. Generate a long random salt using a CSPRNG. The salt should be stored in the user account table alongside the hash and iteration count. Always hash on the server

The sample code uses HMAC-SHA-256 as the core hash inside PBKDF2. The sample source code is available at Github repository.

--

--