Tallan's Technology Blog

Tallan's Top Technologists Share Their Thoughts on Today's Technology Challenges

Applying new NIST standard to Asp.Net Pt. 1 (PBKDF2, SHA256, Password content)

Jeremy Mill

ASP.NET

Introduction

Most developers know that you should never store passwords in plain text, and know that they should be hashed. Only slightly fewer know that they should be stored utilizing a “salt” to append to the password to prevent time trade-off attacks (1). Fewer know what hash function they should use, and it seems lately, the majority don’t know that they shouldn’t just be salting and hashing at all, and instead should be using a key derivation function such as PBKDF2, or scrypt. We will be exploring utilizing PBKDF2, but scrypt is a perfectly viable option. The current draft of the new NIST guidelines says (2):

Verifiers SHALL store memorized secrets in a form that is resistant to offline attacks. Secrets SHALL be hashed with a salt value using an approved hash function such as PBKDF2 as described in [SP800-132]. The salt value SHALL be a 32 bit (or longer) random value generated by an approved random bit generator and is stored along with the hash result. At least 10,000 iterations of the hash function SHOULD be performed. A keyed hash function (e.g., HMAC), with the key stored separately from the hashed authenticators (e.g., in a hardware security module) SHOULD be used to further resist dictionary attacks against the stored hashed authenticators.

This should be read as to say, “use PBKDF2 with a minimum of SHA1, a minimum of a 32 bit salt that’s unique to each password, and a minimum of 10,000 iterations”. Currently, SHA1 is considered secure for use in PBKDF2, because the discovered weaknesses don’t effect the ‘key stretching’ that occurs in PBKDF2. However, the goal of PBKDF2 is to be slow, that is, to make sure that the attacker needs to spend more time breaking a password following a breach. To that end, we will use SHA256 as our HMAC (hashing algorithm) in our implementation for Asp.Net, 20,000 rounds and a 128 bit salt.

Additionally we will cover:

  • Password format and content
  • Password expiration
  • When to (not) make users change their passwords

PasswordHasher

The class we’re creating will handle creating hashed passwords and checking them must implement the AspNet.Identity.IPasswordHasher interface, which specifies two methods. The first is HashPassword, which takes in a password, and returns the hash in string form. The second is VerifyHashedPassword, which takes in the stored hash and a provided password, then returns a PasswordVerificationResult which is basically, a success/failure message for the password attempt. The implementation of PBKDF2 we will be using in this example is found in the Microsoft.AspNetCopre.Cryptography.KeyDerivation NuGet package. It can be installed by typing the following into your package manager console window:

Install-Package Microsoft.AspNetCore.Cryptography.KeyDerivation

Lets look at the code, then work our way through it:

using System;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System.Security.Cryptography;
using Microsoft.AspNet.Identity;
....
public class Pbkdf2Hasher : IPasswordHasher
{
    private KeyDerivationPrf _hmac;
    private int _returnBytes;
    private int _iterations;

    public Pbkdf2Hasher(KeyDerivationPrf hmac = KeyDerivationPrf.HMACSHA256, int iterations = 20000)
    {
        _hmac = hmac;
        //set the return bytes. Number of bytes returned SHOULD NOT EXCEED the number of bytes returned by the HMAC.
        if(_hmac == KeyDerivationPrf.HMACSHA1)
        {
            _returnBytes = 20;
        }
        else if(_hmac == KeyDerivationPrf.HMACSHA256)
        {
            _returnBytes = 32;
        }
        else
        {
            _returnBytes = 64;
        }
        _iterations = iterations;
    }

    public string HashPassword(string password)
    {
        //create a unique salt and salt string
        byte[] salt = new byte[16];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(salt);
        }
        string saltString = Convert.ToBase64String(salt);

        //create the hash passing in the password, the salt, the hmac, the number of iterations and the bytes to be returned
        //then turn that byte array into a base64 string for storage
        byte[] hashBytes = KeyDerivation.Pbkdf2(password, salt, _hmac, _iterations, _returnBytes);
        string hash = Convert.ToBase64String(hashBytes);

        //combine the unique salt and the hash using '|' as a separator.
        //'|' can be used because it never shows up in base64 encoded strings
        string storageString = saltString + "|" + hash;
        //return the new salt+hash string

        return storageString;
    }

    public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
    {
        //split the hash from the salt, like we stored it before
        var storedSalt = hashedPassword.Split('|')[0];
        var storedHash = hashedPassword.Split('|')[1];
        //get the byte arrays of the strings
        byte[] saltBytes = Convert.FromBase64String(storedSalt);
        byte[] hashBytes = Convert.FromBase64String(storedHash);

        //create the hash exactly how we did before, but with the stored salt
        byte[] calcHashBytes = KeyDerivation.Pbkdf2(providedPassword, saltBytes, _hmac, _iterations, _returnBytes);
        string calcHash = Convert.ToBase64String(calcHashBytes);

        //compare the calculated vs the stored. If they're the same, return Success, otherwise fail
        //note, we're not using PasswordVerificationResult.SuccessRehashNeeded,
        //because our passwords never expire, and as of right now, we're not touching bad password filtering
        if (calcHash == storedHash)
        {
            return PasswordVerificationResult.Success;
        }
        else
        {
            return PasswordVerificationResult.Failed;
        }
    }
}

Constructor

The constructor takes in two arguments, an HMAC typpe and a number of iterations. This constructor defaults to SHA256, and 20,000 iterations, which at the time of this writing, is above the minimum NIST recommendations. For those storing passwords for more sensitive applications or the more paranoid, increasing the HMAC to SHA512, or increasing the iterations to a higher number increase the complexity/time required to brute force a hash. For example, LastPass utilizes SHA1 as their HMAC, but uses 100,000 iterations. The code also sets the number of bytes we’re expecting returned by the PBKDF2 function. This should not be set to a higher number of bytes than returned by the HMAC utilized by the PBKDF2 method. That is because it will 1) not increase security, as the attacker will just ignore the extra padding, and 2) will result in a slower application.

HashPassword

This method takes in a string, which represents a users new password, whether they’re changing it or creating a new account, and returns a hash of the user’s password with the salt attached for storage in the database or other data store. The first thing we do is create a salt. The salt is a randomly generated 16 byte string created using the cryptographically secure System.Security.Cryptography.RandomNumberGenerator. We then convert the salt into a base 64 string.

Next we create the hash string by passing our variables into the KeyDerivation.Pbkdf2 method, and turning the resulting byte array into another base 64 encoded string. Finally, we concatenate the strings using a pipe separator (because ‘|’ never shows up in base 64 encoded strings 3). The string we’re returning is what will be stored in the underlying data store (probably a database).

VerifyHashedPassword

This final method takes in two strings, the password the user entered, and the hashed password from the underlying datastore. The stored password is split into it’s hash and salt components, and turned byte into byte arrays. The KeyDerivation.Pbkdf2 method is called again, on the user-entered password with the stored salt, and the hashes are compared. If the hashes match, the passwords must (probably) match. Return success or failure.

It’s worth noting that there is a PasswordVerificationResult that we are not currently utilizing, which is “PasswordVerificationResult.SuccessRehashNeeded”. There are only a few reasons that one should use this result, and there is at least 1 why someone should NOT utilize it. You may use this result if you are:

  • Checking for commonly used passwords such as ‘password’ or ‘123456’
  • Enforcing users to change their password because there has been a security breach.

You should not use this to:

 

Utilizing your new Password Hasher

Utilizing your new password hasher is as simple as editing the IdentityConfig.cs file located in the app_start file. Just add this line to the Create(…) method:

 

manager.PasswordHasher = new Pbkdf2Hasher();

You’re now using PBKDF2! It’s worth mentioning here that changing how you calculate and store password hashes will break any old accounts in your system.

While you’re here in this method, you might as well edit your PasswordValidator methods to fit the NIST specifications as well by configuring the following:

  • RequiredLength = 8
  • RequireNonLetterOrDigit = False
  • RequireDigit = False
  • RequireLowercase = False
  • RequireUppercase = False

While this may feel LESS secure, it’s not. Limiting the keyspace is bad. Period. For example:

tWvNJCLEsv(*-Ha?C(L;%D$jC$~vuhh{

is a good password, that contains 0 numbers.

Notes on performance

It shouldn’t be a surprise that utilizing PBKDF2 to calculate your user’s password hashes has a small hit on performance, mostly because that’s the idea. If this was fast, it would also be fast for an attacker. As a general guide, I’m attaching some data about the performance of calculating hashes utilizing different HMAC’s. The number of iterations for this test was set at 10,000 and the test was performed on a machine with the following specifications:

  • i7-6500 CPU
  • Windows 10
  • .net v4.5.2
  • AspNetCore.Cryptography.KeyDerivation v1.1.0

hash_speed

 

Conclusion

Breaches can happen to anyone, and what you choose for your password storage mechanism is extremely important to how bad that breach will be. A weak storage mechanism means your user’s accounts will be accessed quickly, and not just on your site, but on all of the others where they (unfortunately) re-use their passwords. PBKDF2 is a great option, and is now much more flexible than it was in the past with the release of the Key Derivation class in Asp Net Core.

_________________________________________________________________________________________

To learn more on how Tallan can help your organization with our Custom Software Development capabilities, CLICK HERE to check out our website!

No comments

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

\\\