How to store a password in a web application?

 
 
  • Gérald Barré

This post is part of the series 'Password management'. Be sure to check out the rest of the blog posts of the series!

A password is a very sensitive piece of information. It allows a user to authenticate on an application. You should not store it like any other data. We often see news indicating that passwords have leaked. This has happened to LinkedIn, Adobe, 500px, and unfortunately many more. So you have to make sure that such incidents cannot happen in your applications. Indeed, you are responsible for your data. Also, what will your customers think if your company makes headlines for leakage of user accounts? This is not a subject to be taken lightly.

Here are some strategies to store passwords. Some are no go, but present here to explain why they are wrong.

#❌ Store passwords in clear

Clearly, this idea should not have even come to your mind! Anyone with read access to your database can access this information. While the database should not be accessible to your users in the case of a web application, you never know what can happen. You may be vulnerable to a SQL injection and so expose all your data including the passwords. Maybe your hosting provider has access to the database or a backup. What about a rogue administrator? Also, don't forget that users often reuse the same password across websites. So, if an attacker get access to your database, it may reuse these data to authenticate a user on multiple websites.

#❌ Implement your own algorithm

Security is not as easy as it seems. There are lots of places where you can introduce security flaws. Most of the problems have already been identified with the previous algorithms, and some of them are very tricky. So unless you are a security expert, you should stick with a standard and approved algorithm. Also, you should avoid implementing the standard algorithm by yourself. Instead, you should use the one provided by the operating system, the .NET framework, or any well-tested libraries such as openssl. These implementations are battle-tested and get security fixes if needed.

#❌ Encrypt passwords

This method consists of encrypting the password with a key stored on the server. You can then decrypt the password to compare it with the one entered by the user. This means you can get the actual password of the user using the key.

This method does not correctly protect the password because if your database is compromised there is a good chance that the encryption key is too. The attacker will be able to decrypt the passwords. Also, the same issues as the previous section apply (password reuse, trust in the hosting provider, etc.).

#❌ Hash passwords

A hash function is a one-way function that will always return the same value for a given input. This means that the hash generated by two identical passwords is identical. Instead of storing the password in the database, you can store its hash. Then, to validate that the user has entered their password, you can to calculate the hash of the password entered by the user and to compare it with the hash stored in the database.

There are several hash algorithms such as MD5, SHA1, SHA2, SHA3… However, several algorithms are considered unsafe like MD5 or SHA1. So, do not use them. Check the status of each algorithm when implementing your application. Indeed it can evolve between the date of publication of this post and the moment when you read it.

C#
public static string HashPassword(string password, string algorithm = "sha256")
{
   return Hash(Encoding.UTF8.GetBytes(password), algorithm);
}

private static string Hash(byte[] input, string algorithm = "sha256")
{
    using (var hashAlgorithm = HashAlgorithm.Create(algorithm))
    {
        return Convert.ToBase64String(hashAlgorithm.ComputeHash(input));
    }
}

A hash function is a one-way function. So, you should not be able to find the original password from the hash. That's for the theory. In practice, you can use Rainbow Tables. A rainbow table is a precomputed table for reversing cryptographic hash functions. Tables are usually used in recovering a password up to a certain length consisting of a limited set of characters. You can use a tool such as John The Ripper or hashcat to test how fast it is to get the original password from a hash.

#✓ Hash passwords with salt

A solution to prevent the usage of Rainbow tables is to add random data (salt) to the password before calling the hash function. This way, you need to reverse the function hash(salt + password) to get the actual password. As you cannot generate a rainbow table for every possible salt + password, there is no easy way to reverse the hash function. The only way is to use a brute-force attack with the given salt.

How to store a password?

  1. Generate a new random salt
  2. Compute hash(salt + password)
  3. Save the salt and the result of the hash function.

How to validate a password?

  1. Read the user's salt from the database
  2. Compute hash(salt + password)
  3. Compare the result of the hash function with the hash in the database

A few things about security:

  • The salt must be unique per password!
  • The salt must be long enough, so you are sure there is no existing rainbow table. 128 bits seem ok at the moment.

C#
public sealed class PasswordHasher
{
    public byte Version => 1;
    public int SaltSize { get; } = 128 / 8; // 128 bits
    public HashAlgorithmName HashAlgorithmName { get; } = HashAlgorithmName.SHA256;

    public string HashPassword(string password)
    {
        if (password == null)
            throw new ArgumentNullException(nameof(password));

        // The salt must be unique for each password
        byte[] salt = GenerateSalt(SaltSize);
        byte[] hash = HashPasswordWithSalt(password, salt);

        var inArray = new byte[1 + SaltSize + hash.Length];
        inArray[0] = Version;
        Buffer.BlockCopy(salt, 0, inArray, 1, SaltSize);
        Buffer.BlockCopy(hash, 0, inArray, 1 + SaltSize, hash.Length);

        return Convert.ToBase64String(inArray);
    }

    public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string password)
    {
        if (password == null)
            throw new ArgumentNullException(nameof(password));

        if (hashedPassword == null)
            return PasswordVerificationResult.Failed;

        Span<byte> numArray = Convert.FromBase64String(hashedPassword);
        if (numArray.Length < 1)
            return PasswordVerificationResult.Failed;

        byte version = numArray[0];
        if (version > Version)
            return PasswordVerificationResult.Failed;

        var salt = numArray.Slice(1, SaltSize).ToArray();
        var bytes = numArray.Slice(1 + SaltSize).ToArray();

        var hash = HashPasswordWithSalt(password, salt);

        if (FixedTimeEquals(hash, bytes))
            return PasswordVerificationResult.Success;

        return PasswordVerificationResult.Failed;
    }

    private byte[] HashPasswordWithSalt(string password, byte[] salt)
    {
        byte[] hash;
        using (var hashAlgorithm = HashAlgorithm.Create(HashAlgorithmName.Name))
        {
            byte[] input = Encoding.UTF8.GetBytes(password);
            hashAlgorithm.TransformBlock(salt, 0, salt.Length, salt, 0);
            hashAlgorithm.TransformFinalBlock(input, 0, input.Length);
            hash = hashAlgorithm.Hash;
        }

        return hash;
    }

    private static byte[] GenerateSalt(int byteLength)
    {
        using (var cryptoServiceProvider = new RNGCryptoServiceProvider())
        {
            var data = new byte[byteLength];
            cryptoServiceProvider.GetBytes(data);
            return data;
        }
    }

    // In .NET Core 2.1, you can use CryptographicOperations.FixedTimeEquals
    // https://github.com/dotnet/runtime/blob/419e949d258ecee4c40a460fb09c66d974229623/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptographicOperations.cs#L32
    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    public static bool FixedTimeEquals(byte[] left, byte[] right)
    {
        // NoOptimization because we want this method to be exactly as non-short-circuiting as written.
        // NoInlining because the NoOptimization would get lost if the method got inlined.
        if (left.Length != right.Length)
        {
            return false;
        }

        int length = left.Length;
        int accum = 0;

        for (int i = 0; i < length; i++)
        {
            accum |= left[i] - right[i];
        }

        return accum == 0;
    }
}

public enum PasswordVerificationResult
{
    Failed,
    Success,
    SuccessRehashNeeded,
}

#✓✓ Use specialized hashing algorithms

There are specialized algorithms for hashing passwords such as bcrypt, scrypt or PBKDF2. These algorithms take longer than MD5 or SHA1 to compute a hash (so they are more robust to brute-forcing). Their cost ("work factor") is represented by a number of iterations. This number is configurable and must be adapted as needed (please read the recommendation of the algorithm and recommendation of some public agency such as NIST). The lower it is, the easier it will be to find the password. The higher it is, the more computing power is needed. You must choose a value above the current recommendations but not too much higher to not degrade the user experience, nor require overpowered servers.

Using hashcat, you can see how many hashes per second you can generate. Here're the numbers on my computer:

Hash computed per secondsHash computed per seconds

You can see that specialized algorithms are much slower than classic hash algorithms. This means that it will take far much time to find the original password by a brute-force attack.

Here's an example of using PBKDF2 in C#. The following code also provides a way to rehash passwords if you change the number of iterations.

C#
public sealed class PasswordHasher
{
    public byte Version => 1;
    public int Pbkdf2IterCount { get; } = 50000;
    public int Pbkdf2SubkeyLength { get; } = 256 / 8; // 256 bits
    public int SaltSize { get; } = 128 / 8; // 128 bits
    public HashAlgorithmName HashAlgorithmName { get; } = HashAlgorithmName.SHA256;

    public string HashPassword(string password)
    {
        if (password == null)
            throw new ArgumentNullException(nameof(password));

        byte[] salt;
        byte[] bytes;
        using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize, Pbkdf2IterCount, HashAlgorithmName))
        {
            salt = rfc2898DeriveBytes.Salt;
            bytes = rfc2898DeriveBytes.GetBytes(Pbkdf2SubkeyLength);
        }

        var inArray = new byte[1 + SaltSize + Pbkdf2SubkeyLength];
        inArray[0] = Version;
        Buffer.BlockCopy(salt, 0, inArray, 1, SaltSize);
        Buffer.BlockCopy(bytes, 0, inArray, 1 + SaltSize, Pbkdf2SubkeyLength);

        return Convert.ToBase64String(inArray);
    }

    public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string password)
    {
        if (password == null)
            throw new ArgumentNullException(nameof(password));

        if (hashedPassword == null)
            return PasswordVerificationResult.Failed;

        byte[] numArray = Convert.FromBase64String(hashedPassword);
        if (numArray.Length < 1)
            return PasswordVerificationResult.Failed;

        byte version = numArray[0];
        if (version > Version)
            return PasswordVerificationResult.Failed;

        byte[] salt = new byte[SaltSize];
        Buffer.BlockCopy(numArray, 1, salt, 0, SaltSize);
        byte[] a = new byte[Pbkdf2SubkeyLength];
        Buffer.BlockCopy(numArray, 1 + SaltSize, a, 0, Pbkdf2SubkeyLength);
        byte[] bytes;
        using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, Pbkdf2IterCount, HashAlgorithmName))
        {
            bytes = rfc2898DeriveBytes.GetBytes(Pbkdf2SubkeyLength);
        }

        if (FixedTimeEquals(a, bytes))
            return PasswordVerificationResult.Success;

        return PasswordVerificationResult.Failed;
    }

    // In .NET Core 2.1, you can use CryptographicOperations.FixedTimeEquals
    // https://github.com/dotnet/runtime/blob/419e949d258ecee4c40a460fb09c66d974229623/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptographicOperations.cs#L32
    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    public static bool FixedTimeEquals(byte[] left, byte[] right)
    {
        // NoOptimization because we want this method to be exactly as non-short-circuiting as written.
        // NoInlining because the NoOptimization would get lost if the method got inlined.
        if (left.Length != right.Length)
        {
            return false;
        }

        int length = left.Length;
        int accum = 0;

        for (int i = 0; i < length; i++)
        {
            accum |= left[i] - right[i];
        }

        return accum == 0;
    }
}

public enum PasswordVerificationResult
{
    Failed,
    Success,
    SuccessRehashNeeded,
}

I don't use SuccessRehashNeeded in this example, but you would need it if you change the version of the algorithm. For instance, when you create your application the recommendations indicates you can safely use PBKDF2 with 50000 iterations. 2 years later the recommendations change and you want to update the password hasher to reflect the new way to hash password. As you won't be able to update all saved passwords (you cannot get the original password from the hash), you need to temporarily handle both algorithms (v1 and v2). When you validate the password using the first version of the password hasher, you will return SuccessRehashNeeded if the password is valid. At this time, you have the actual password, so you can use the latest version of the password hasher to compute the new hash and save it.

#⚠ Beware of timing attacks

In the previous example, I use a non-optimal way to compare 2 byte arrays (FixedTimeEquals). Indeed, it compares all entries of the array even if the first ones are different. The idea is to prevent an attacker from using a timing attack to guess the password. Indeed, if you return early, the attacker can detect the beginning of the hash is not valid, and may be able to guess more quickly the password. FixedTimeEquals compares the values of the 2 arrays in a fixed time whether there are equal or not.

Read more: Practical Uses for Timing Attacks on Hash Comparisons (e.g. MD5)?

C#
// ❌ do not use this method to compare passwords
private static bool BadByteArraysEqual(byte[] a, byte[] b)
{
    if (a == null || b == null || a.Length != b.Length)
        return false;

    for (int index = 0; index < a.Length; ++index)
    {
        if (a[index] != b[index])
            return false; // early exit
    }

    return true;
}

Instead, you should use the following method. Don't forget the MethodImpl attributes to ensure the method is not inlined and the JIT does not try to optimize the method.

C#
// In .NET Core 2.1, you can use CryptographicOperations.FixedTimeEquals
// https://github.com/dotnet/runtime/blob/419e949d258ecee4c40a460fb09c66d974229623/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptographicOperations.cs#L32
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static bool FixedTimeEquals(byte[] left, byte[] right)
{
    // NoOptimization because we want this method to be exactly as non-short-circuiting as written.
    // NoInlining because the NoOptimization would get lost if the method got inlined.

    if (left.Length != right.Length)
        return false;

    int length = left.Length;
    int accum = 0;

    for (int i = 0; i < length; i++)
    {
        accum |= left[i] - right[i];
    }

    return accum == 0;
}

#✓✓ Get rid of passwords

You can avoid storing passwords by using authentication scheme that don't require processing passwords on the server.

You can rely on other identity providers to handle authentification using OpenId or OpenId Connect (OIDC). This way you let other providers, such as Microsoft, Google, or Apple, handle the password management (and all the complexity of authenticating a user).

WebAuthn allows users to log into internet accounts using their preferred device. Web services and apps can – and should – turn on this functionality to give their users an easier login experience via biometrics, mobile devices and/or FIDO security keys — and with much higher security over passwords alone.

OPAQUE is a protocol to authenticate a user while keeping password on the client machine. This way, the server never knows the actual user password.

#Conclusion

You should use modern ways to authenticate users that do not imply passwords if possible. If you still need to store the password, you must use a one-way function that turns the password and a unique salt into a hash. This transformation must be computing intensive to avoid brute-force attacks (but not too long to not kill your server). You should use specialized algorithms, such as PBKDF2 or bcrypt, to prevent bad practices.

Do not forget that security is not only about storing passwords. There are so many other things you must pay attention to. For instance, if the connection between the client and the server is not secure someone can intercept the password before it reaches the server. You can prevent that by using https to secure the connection. This is only one of the possible problems. Don't forget, security is hard

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub