View on GitHub

PasswordHasher

An ASP.NET Core implementation for hashing passwords without using TUser and PasswordVerificationResult, which is in the standard implementation. SHA384 and SHA512 hashing algorithms are also supported.

Did you like how the PasswordHasher was implemented in ASP.NET Core? What is the <TUser> used for in class definition? Why only SHA1 and SHA256 are supported? Why the size of salt is a constant? Is the default ASP.NET Core implementation a good choice?

PasswordHasher in ASP.NET Core could be better implemented

If you are not familiar with the default implementation of PasswordHasher in ASP.NET Core, I would like to recommend an excellent article which explains everything.

Some ideas for better implementation of PasswordHasher:

Implementation

Build Status

The default password hasher for ASP.NET Core Identity uses PBKDF2 for password hashing that is not support all hashing algorithms. The Rfc2898DeriveBytes class from the System.Security.Cryptography namespace supports all that we need to get the result we wanted. This class can generate pseudo-randomized salt and supports all SHA hashing algorithms.

Method for hashing passwords

public string HashPassword(string password)
{
    byte[] saltBuffer;
    byte[] hashBuffer;
    
    using (var keyDerivation = new Rfc2898DeriveBytes(password, options.SaltSize, options.Iterations, options.HashAlgorithmName))
    {
        saltBuffer = keyDerivation.Salt;
        hashBuffer = keyDerivation.GetBytes(options.HashSize);
    }
    
    byte[] result = new byte[options.HashSize + options.SaltSize];
    Buffer.BlockCopy(hashBuffer, 0, result, 0, options.HashSize);
    Buffer.BlockCopy(saltBuffer, 0, result, options.HashSize, options.SaltSize);
    return Convert.ToBase64String(result);
}

Method for verifing the hash and passwords

public bool VerifyHashedPassword(string hashedPassword, string providedPassword)
{
    byte[] hashedPasswordBytes = Convert.FromBase64String(hashedPassword);
    if (hashedPasswordBytes.Length != options.HashSize + options.SaltSize)
    {
            return false;
    }

    byte[] hashBytes = new byte[options.HashSize];
    Buffer.BlockCopy(hashedPasswordBytes, 0, hashBytes, 0, options.HashSize);
    byte[] saltBytes = new byte[options.SaltSize];
    Buffer.BlockCopy(hashedPasswordBytes, options.HashSize, saltBytes, 0, options.SaltSize);

    byte[] providedHashBytes;
    using (var keyDerivation = new Rfc2898DeriveBytes(providedPassword, saltBytes, options.Iterations, options.HashAlgorithmName))
    {
            providedHashBytes = keyDerivation.GetBytes(options.HashSize);
    }

    return comparer.Equals(hashBytes, providedHashBytes);
}

Setting up

The parameters for PasswordHasher can be specified in Startup.cs via Options pattern. Also, in Startup.cs can be registered the PasswordHasher as a microservice.

public void ConfigureServices(IServiceCollection services)
{
    // Configuring PasswordHasher
    services.Configure<PasswordHasherOptions>(options =>
    {
        options.HashAlgorithm = PasswordHasherAlgorithms.SHA1;
        options.SaltSize = 16;
        options.Iterations = 8192;
    });

    // Registering PasswordHasher
    services.AddPasswordHasher();
    
    services.AddMvc();
}

Usage example

public class IndexModel : PageModel
{
    private readonly IPasswordHasher hasher;

    public IndexModel(IPasswordHasher hasher)
    {
        this.hasher = hasher;
    }
    
    public void OnGet()
    {
        var password = "my password";
        var passwordHash = hasher.HashPassword(password);
        var passwordCheck = hasher.VerifyHashedPassword(passwordHash, password);
    }
}

Support or Contact

Having questions? Contact me and I will help you sort it out.