How to store a password in a web application?

  • .NET
  • Security

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. In addition, 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 the passwords. Some are clearly 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 an expert in security, 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 implementation 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.

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 to the password before calling the hash function. So, the actual password won't exist in the Rainbow Tables, and tools won't be able to reverse it (or at least it will be much slower). This value is called a "salt". For example, if the password is foo we are going to generate a string of random characters krghjéàhhjbgUYf42575gdj that we are going to concatenate to the password krghjéàùhvjbgUYf42575gdjfoo and hash this value. When you validate the password, you have to read the salt from the database, concatenate it to the password, hash the result, and check if it corresponds to the value in the database. The salt must be long enough so that it is no longer possible to use a Rainbow Table (too long to generate and requiring too much disk space to save it). A salt of 128 bits seems sufficient.

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));

        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/corefx/blob/a10890f4ffe0fadf090c922578ba0e606ebdd16c/src/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). In fact, 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 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 per seconds

You can see that specialized algorithms are much slower than classic hash algorithm. 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 password if you change the number of iterations.

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/corefx/blob/a10890f4ffe0fadf090c922578ba0e606ebdd16c/src/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 recommandations 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 temporarly handle both algorithm (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)?

// ❌ 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.

// In .NET Core 2.1, you can use CryptographicOperations.FixedTimeEquals
// https://github.com/dotnet/corefx/blob/a10890f4ffe0fadf090c922578ba0e606ebdd16c/src/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;
}

Conclusion

To sum it up 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 password. 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

Do you have a question or a suggestion about this post? Contact me on Twitter or by email!

Follow me:
Enjoy this blog?Buy Me A CoffeeDonate with PayPal

Comments

Jc -

Hi there, thanks for this article.

I have two questions. If you use a different salt for each password, where do you store the salt? I imagine that storing salt with the hashed password isn't a good practice 😏.

In your last exemple you have a SuccessRehashNeeded enum value. In which kind of situation we may need to rehash a succeeded password?

Thanks

Gérald Barré -

Hi Jc,

Thanks for your comment! I've updated both sections to answer your questions 😃 Let me know if you have other questions?

Jc -

Thanks for your update Gérald. As we could see the test about the version, I should have understood the RehasNeed ^^'. I asked about storing salts because for the encryption method you said: "if your database is compromised there is a good chance that the encryption key is too." So I understand that in the hashed + salt method, even if we have the salt, the password stay hashed, but if the database is compromised, the password becomes vulnerable to rainbow tables again.