Secure Password Storage
Last updated
Last updated
In software development we constantly use password-based user authentication and we need to store user passwords securely in a way that users can sign-up, authenticate and change their password, but in the same time attackers cannot decrypt the stored passwords back to clear text values, even if they manage to get access to the database holding the user accounts.
If we develop a Web site or Web app, we typically have admin panel, accessible after login, based on username + password. It is similar for mobile apps, web services and other password-protected systems: all they need secure password storage (secure password management / strongly encrypted password storage).
Developers often store the user passwords for their sites, apps or other systems in a database, just like any other user data, but most systems apply some kind of hashing, encryption or password authentication scheme. There are many ways to implement the password storage for password-based authentication. The most popular of them are given in the table below:
Let's review these password storage methods and discuss their level of security, their strong and weak sides.
The easiest and most highly insecure method for password storage and password-based authentication is to use clear-text passwords written directly in the database.
In this scenario to check the password, developers just compare the password for checking with the password from the database.
Never do this!!! It is anti-pattern for software development. It is bad for many reasons.
Admins will be able to see user's passwords and this is really bad, because many users use the same password for several sites / apps, e.g. the same password for GMail, Facebook and Twitter.
Admins should never know user's passwords, but should be able to change them in case of emergency.
If someone hacks the server and gains access to the database, he will see all user's passwords in plaintext.
It is very bad practice to keep plaintext passwords in any information system / app in the world!
Just don't do it!
A relatively easy and relatively insecure method for password storage and password-based authentication is to use simple password hash like SHA-256(password), written directly in the database.
In this scenario to check the password, developers just compare the hash(password for checking) with the password hash from the database.
Avoid this! It is highly insecure method.
Why? Because hashes are vulnerable to dictionary attacks.
Crackers who gain access to the database, can use a dictionary holding the hashes of the most commonly used 10-20 million passwords and most of the passwords will be decrypted.
The dictionary attack process is extremely fast, because it compares the hashes from the dictionary with the password hash (trivial string compare).
Search in Internet for free dictionaries / wordlists for dictionary attack.
Cracked passwords, revealed as plaintext are a true disaster, because most users use the same password for several sites / apps.
More complicated and relatively secure method for password storage and password-based authentication is to use salted hashed passwords, written in the database as pair { salt + hash(password + salt) }. The hash function can be any strong cryptographic hash like SHA-256.
The idea is to keep different random salt, along with different password hash, changed every time, when the password is written in the database. Thus the same password is encrypted every time as different ciphertext { salt + hash }.
To check the password, developers calculate the hash(password for checking) using the salt from the database and compare the calculated hash with the hash from the database.
This method works well to prevent dictionary attacks, but does not prevent GPU-based and ASIC-based brute force password cracking attacks.
It has also the same security problems like using hash(key + msg) instead of HMAC(key, msg), e.g. length-extension attack.
Basically keeping salted hashed passwords is more secure than the previous methods, but still avoid it.
Use more attack-resistant password hashing function (which is unlikely to be brute-forced) instead of simple hash, e.g. Argon2 or Scrypt.
The most complicated and most secure method for secure password storage and password-based authentication is to use secure KDF-based password hash, written in the database as pair { salt + KDF(password, salt) }. The key-derivation function (KDF) should be strong and secure, e.g. Scrypt or Argon2 with carefully selected parameters.
The idea is to keep different random salt for each encrypted password, along with the key derived by a secure KDF-function, such as Scrypt or Argon2 (with reasonable number of iterations and RAM consumption settings).
To check the password, take the salt from the database and derive a key from the password for checking, using the same KDF function and KDF parameters like when the password was stored in the database. Compare the derived key with the key from the database.
This method is resistant to most attacks and is considered as standard in the software industry.
It is as secure as the KDF function with the selected KDF parameters. For example, Argon2 taking 128 MB RAM, 4 threads and 200 ms computing time) may be appropriate for the average case.
Crackers cannot perform brute force attacks, because the password guessing will be too slow, even on a modern computer with good CPU and GPU or even using a specialized ASIC hardware.
Conclusion: use secure KDF functions like Argon2 and Scrypt to keep encrypted passwords in the database. Never use plain-text passwords!
Using a secure password storage is only one of the components of the process of secure password-based authentication for Web apps, mobile apps and Internet services. Systems, that use password-based-authentication are subject of many attacks:
Password guessing attack: attacker tries to guess / brute-force the user's password by attempting many logins in parallel.
Solved easily by adding increasing login delay (wait time before the login is available again) after each wrong login attempt or even temporary account locking. Delays / locking should be done by IP address + username, to avoid login problems for the legitimate users.
Secure KDF-based password storage delays the password guessing process, so it is highly recommended.
Using a CAPTCHA after a 2-3 unsuccessful login attempts provides quite good protection.
Denial of service attack: attacker may attempt to login too many times to overload the system or can try to lock some user account with too many invalid login attempts for the same user.
The protection from this attack is similar to the previous attack: use a CAPTCHA and delay the login process for certain IP address after each login attempt.
Intercept and replay attack: attacker may intercept the authentication communication (to sniff the login / password / auth ticket / other credentials) and use the intercepted credentials to login later.
Most systems solve this problem by using TLS (encrypted connection) to securely send the authentication credentials (password / authentication ticket) to the server.
Other solutions include challenge-response based cryptographic authentication scheme, such as the scheme used in the Kerberos protocol.
Man-in-the-middle attack: attacker can intercept and modify the intercepted traffic between the server and the client to trick the user to reveal its login credentials.
This is solved by using a TLS secure connection with server certificate, which authenticates the server.
In some scenarios (e.g. online banking) clients are also authenticated by a digital certificate or OTP (one-time password).
Compromised server attack: if the authentication server and its database is compromised (hacked) and all its authentication data is leaked, the attacker should be unable to reveal user's plaintext passwords.
First, it should be clear that if the authentication server is compromised, in all cases the attackers will get unauthorised access, because they will be able to intercept user's legitimate sessions (their login and communication after successful login) and use them to impersonate the user.
Using a strongly secure password storage mechanism mitigates the risk for users' passwords to be revealed as plaintext. Still, attackers who gain access to the authentication server may inject password interception backdoor and steal each user's plaintext credentials (username + password) during the login.
The backdoored server attack can be stopped like this: the client generates a random number r and sends as authentication HMAC(password, r); the server compares the HMAC with its stored password. This process may be combined with client-side Scrypt or Argon2 computation and securely stored password at the server side (Scrypt or Argon2 hashed). In this scenario, unless the client software is not compromised, attackers who gained access to the authentication server will not obtain user's passwords in plaintext.
In Web applications, if the server is compromised it can inject JavaScript code to compromise the client itself. In desktop and mobiles apps, the client is more safe in case of compromised server.
Conclusions about how to implement a secure password-based authentication for Web sites, apps and services:
Use secure password storage, such as Argon2 hash, with strong enough configuration settings.
Keep your authentication server as secure as possible. If it is compromised, the entire system will be compromised.
Use TLS or other secure communication channel between the authentication server and the client.
Approach
Security
Comments
Clear-text passwords
Extremely low
Never do this: compromised server will render all passwords leaked
Simple password hash
Low
Vulnerable to dictionary attacks
Salted hashed passwords
Average
Vulnerable to GPU-based and ASIC-based password cracking
Secure KDF function (like Argon2)
High
Recommended, use strong KDF parameters