jpshook
jpshook

Reputation: 4944

How can I hash passwords with salt and iterations using PBKDF2 HMAC SHA-256 or SHA-512 in C#?

I would like to find a solution or method that will allow me to add salt and control the number of iterations. The native Rfc2898DeriveBytes is based on HMACSHA1. Ideally, using SHA-256 or SHA-512 will make the system future proof.

This is the best example I have found so far: http://jmedved.com/2012/04/pbkdf2-with-sha-256-and-others/ but when I ran it with SHA-256 it was actually slower than with SHA-512. I used 64k iterations, a guid for salt and different same length passwords to compare.

I also found this solution: http://sourceforge.net/projects/pwdtknet/ which has full source code available. It seems to be more robust.

So far I am not able to get the same output from each of them.

Upvotes: 11

Views: 10378

Answers (6)

ovolko
ovolko

Reputation: 2807

The more recent alternative is Microsoft.AspNetCore.Cryptography.KeyDerivation NuGet package, which allows to use PBKDF2 with SHA-256 and SHA-512 hash functions, which are stronger than SHA-1 which is built into Rfc2898DeriveBytes. The advantage over third-party libraries mentioned in other answers is that it's implemented by Microsoft, so you don't need to perform a security audit for it, once you already rely on .NET platform. Documentation is available at learn.microsoft.com.

Upvotes: 1

Chris Marisic
Chris Marisic

Reputation: 33128

This is provided by SecurityDriven.NET's Inferno library.

Install-Package Inferno

Inferno promotes SHA-384 given it's usage by the NSA Suite B for protecting top secret information and "its truncated design serves as an effective defense against length extension attacks" (1).

using SecurityDriven.Inferno;
using SecurityDriven.Inferno.Extensions;
using static SecurityDriven.Inferno.SuiteB;
using static SecurityDriven.Inferno.Utils;
using PBKDF2 = SecurityDriven.Inferno.Kdf.PBKDF2;

Storing an user's password:

var sha384Factory = HmacFactory;
var random = new CryptoRandom();

byte[] derivedKey
string hashedPassword = null;
string passwordText = "foo";

byte[] passwordBytes = SafeUTF8.GetBytes(passwordText);
var salt = random.NextBytes(384/8);

using (var pbkdf2 = new PBKDF2(sha384Factory, passwordBytes, salt, 256*1000))
    derivedKey=  pbkdf2.GetBytes(384/8);


using (var hmac = sha384Factory()) 
{
    hmac.Key = derivedKey;
    hashedPassword = hmac.ComputeHash(passwordBytes).ToBase16();
}

Persist both salt and hashedPassword. Note you can persist them either as binrary or use the helper to store them as strings. Note that salt is created randomly.

Verify a user's sign on:

var user = GetUserByUserName("bob")

var sha384Factory = HmacFactory;

byte[] derivedKey
string hashedPassword = null;
string suppliedPassword = "foo";

byte[] passwordBytes = SafeUTF8.GetBytes(suppliedPassword);

using (var pbkdf2 = new PBKDF2(sha384Factory, passwordBytes, user.UserSalt, 256*1000))
    derivedKey=  pbkdf2.GetBytes(384/8);


using (var hmac = sha384Factory()) 
{
    hmac.Key = derivedKey;
    hashedPassword = hmac.ComputeHash(passwordBytes).ToBase16();
}

isAuthenticated = hashedPassword == user.UserHashedPassword; //true for bob

As you can see here, the process is nearly identical. The key difference being that there's no usage of CryptoRandom and we use persisted UserSalt when creating the PBKDF2 instance.

Source on GitHub

Upvotes: 2

JimmiTh
JimmiTh

Reputation: 7449

Another implementation - from before I found others like RoadWarrior, Zer and thasiznets had already done it.

This, like Rfc2898DeriveBytes derives from .NET's System.Cryptography.DeriveBytes. In other words, usage is the same - although I only implemented the one constructor I use.

Other than that lineage, it's not based on Microsoft's implementation at all. Which also calls for a disclaimer - see bottom of this answer.

It allows an arbitrary Pseudo Random Function, which means we can plug in HMAC SHA256 or HMAC SHA512 - or someone with more cryptographic insight and courage than me could plug in whatever they want - like the RFC allows. It also uses long rather than int for the iteration count - just for the crazy ones.

/// <summary>
/// More generic version of the built-in Rfc2898DeriveBytes class. This one
/// allows an arbitrary Pseudo Random Function, meaning we can use e.g. 
/// HMAC SHA256 or HMAC SHA512 rather than the hardcoded HMAC SHA-1 of the 
/// built-in version.
/// </summary>
public class PBKDF2DeriveBytes : DeriveBytes
{
    // Initialization:

    private readonly IPseudoRandomFunction prf;
    private readonly byte[] salt;
    private readonly long iterationCount;

    private readonly byte[] saltAndBlockNumber;

    // State:

    // Last result of prf.Transform - also used as buffer
    // between GetBytes() calls:
    private byte[] buffer;

    private int bufferIndex;
    private int nextBlock;

    /// <param name="prf">
    ///    The Pseudo Random Function to use for calculating the derived key
    /// </param>
    /// <param name="salt">
    ///    The initial salt to use in calculating the derived key
    /// </param>
    /// <param name="iterationCount">
    ///    Number of iterations. RFC 2898 recommends a minimum of 1000
    ///    iterations (in the year 2000) ideally with number of iterations
    ///    adjusted on a regular basis (e.g. each year).
    /// </param>
    public PBKDF2DeriveBytes(
       IPseudoRandomFunction prf, byte[] salt, long iterationCount)
    {
        if (prf == null)
        {
            throw new ArgumentNullException("prf");
        }

        if (salt == null)
        {
            throw new ArgumentNullException("salt");
        }

        this.prf = prf;
        this.salt = salt;
        this.iterationCount = iterationCount;

        // Prepare combined salt = concat(original salt, block number)
        saltAndBlockNumber = new byte[salt.Length + 4];
        Buffer.BlockCopy(salt, 0, saltAndBlockNumber, 0, salt.Length);

        Reset();
    }

    /// <summary>
    ///    Retrieves a derived key of the length specified.
    ///    Successive calls to GetBytes will return different results -
    ///    calling GetBytes(20) twice is equivalent to calling
    ///    GetBytes(40) once. Use Reset method to clear state.
    /// </summary>
    /// <param name="keyLength">
    ///    The number of bytes required. Note that for password hashing, a
    ///    key length greater than the output length of the underlying Pseudo
    ///    Random Function is redundant and does not increase security.
    /// </param>
    /// <returns>The derived key</returns>
    public override byte[] GetBytes(int keyLength)
    {
        var result = new byte[keyLength];

        int resultIndex = 0;

        // If we have bytes in buffer from previous run, use those first:
        if (buffer != null && bufferIndex > 0)
        {
            int bufferRemaining = prf.HashSize - bufferIndex;

            // Take at most keyLength bytes from the buffer:
            int bytesFromBuffer = Math.Min(bufferRemaining, keyLength);

            if (bytesFromBuffer > 0)
            {
                Buffer.BlockCopy(buffer, bufferIndex, result, 0,
                   bytesFromBuffer);
                bufferIndex += bytesFromBuffer;
                resultIndex += bytesFromBuffer;
            }
        }

        // If, after filling from buffer, we need more bytes to fill
        // the result, they need to be computed:
        if (resultIndex < keyLength)
        {
            ComputeBlocks(result, resultIndex);

            // If we used the entire buffer, reset index:
            if (bufferIndex == prf.HashSize)
            {
                bufferIndex = 0;
            }
        }

        return result;
    }

    /// <summary>
    ///    Resets state. The next call to GetBytes will return the same
    ///    result as an initial call to GetBytes.
    ///    Sealed since it's called from constructor.
    /// </summary>
    public sealed override void Reset()
    {
        buffer = null;
        bufferIndex = 0;
        nextBlock = 1;
    }

    private void ComputeBlocks(byte[] result, int resultIndex)
    {
        int currentBlock = nextBlock;

        // Keep computing blocks until we've filled the result array:
        while (resultIndex < result.Length)
        {
            // Run iterations for block:
            F(currentBlock);

            // Populate result array with the block, but only as many bytes
            // as are needed - keep the rest in buffer:
            int bytesFromBuffer = Math.Min(
                   prf.HashSize,
                   result.Length - resultIndex
            );
            Buffer.BlockCopy(buffer, 0, result, resultIndex, bytesFromBuffer);

            bufferIndex = bytesFromBuffer;
            resultIndex += bytesFromBuffer;
            currentBlock++;
        }
        nextBlock = currentBlock;
    }

    private void F(int currentBlock)
    {
        // First iteration:
        // Populate initial salt with the current block index:
        Buffer.BlockCopy(
           BlockNumberToBytes(currentBlock), 0, 
           saltAndBlockNumber, salt.Length, 4
        );

        buffer = prf.Transform(saltAndBlockNumber);

        // Remaining iterations:
        byte[] result = buffer;
        for (long iteration = 2; iteration <= iterationCount; iteration++)
        {
            // Note that the PRF transform takes the immediate result of the
            // last iteration, not the combined result (in buffer):
            result = prf.Transform(result);

            for (int byteIndex = 0; byteIndex < buffer.Length; byteIndex++)
            {
                buffer[byteIndex] ^= result[byteIndex];
            }
        }
    }

    private static byte[] BlockNumberToBytes(int blockNumber)
    {
        byte[] result = BitConverter.GetBytes(blockNumber);

        // Make sure the result is big endian:
        if (BitConverter.IsLittleEndian)
        {
            Array.Reverse(result);
        }

        return result;
    }
}

IPseudoRandomFunction is declared as:

public interface IPseudoRandomFunction : IDisposable
{
    int HashSize { get; }
    byte[] Transform(byte[] input);
}

An example HMAC-SHA512 IPseudoRandomFunction (for brevity - I use a generic class allowing any of .NET's HMAC classes):

public class HMACSHA512PseudoRandomFunction : IPseudoRandomFunction
{
    private HMAC hmac;
    private bool disposed;

    public HmacPseudoRandomFunction(byte[] input)
    {
        hmac = new HMACSHA512(input);
    }

    public int HashSize
    {
        // Might as well return a constant 64
        get { return hmac.HashSize / 8; }
    }

    public byte[] Transform(byte[] input)
    {
        return hmac.ComputeHash(input);
    }

    public void Dispose()
    {
        if (!disposed)
        {
            hmac.Dispose();
            hmac = null;
            disposed = true;
        }
    }
}

Result... This:

using (var prf = new HMACSHA512PseudoRandomFunction(input))
{
    using (var hash = new PBKDF2DeriveBytes(prf, salt, 1000))
    {
        hash.GetBytes(32);
    }
}

... is the HMAC-SHA512 equivalent of this:

using (var hash = new Rfc2898DeriveBytes(input, salt, 1000))
{
    hash.GetBytes(32);
}

Testing

The PBKDF2DeriveBytes class has been tested for

It has also been run through simple tests of Reset() and multiple calls to GetBytes().

A few preliminary performance tests reveal it to be on par with the .NET implementation for SHA-1 for 1000 runs of 1000 iterations on "pass"/"saltSALT" converted to bytes in ASCII encoding with GetBytes(200). Sometimes a little faster than the built-in implementation, sometimes a little slower - we're talking something like 84 vs. 83 seconds on my ancient computer. All of those were done with a debug build of PBKDF2DeriveBytes, though (since the bulk of the work is obviously done in the HMAC, we'd need many more iterations or runs to measure an actual difference anyway).

Disclaimer:

I'm not a cryptography genius. As the above indicates, this has not been heavily tested. I make no guarantees. But maybe, along with the other answers and implementations, it can help in understanding the methodology.

Upvotes: 1

James
James

Reputation: 1904

My CryptSharp library can do PBKDF2 with any arbitrary HMAC. Salt and iterations can be controlled. Look in the CryptSharp.Utility namespace. It's there along with a C# Scrypt implementation and a couple other things.

Upvotes: 2

HTTP 410
HTTP 410

Reputation: 17618

My open-source C# password utilities library on Google Code currently does HMAC SHA1-160 and HMAC SHA2-256, together with salt and iterations (PKDBF2). Timings for password and hash production is built-in to the library, as demonstrated by the accompanying Windows Forms gui.

My code currently takes 0.80 seconds on my machine to do SHA2-256 hash with 65,536 iterations. It could definitely be more efficient as I haven't profiled it yet.

My SHA2-256 code produces the same test results as shown here.

Upvotes: 1

jpshook
jpshook

Reputation: 4944

The PWDTK.NET library (http://sourceforge.net/projects/pwdtknet/) seems to be the only implementation I can find that does PBKDF2 HMAC SHA-512 and allows for salt and iterations. I have not been able to locate test vectors for PBKDF2 HMAC SHA-512 to test with.

I am surprised that there are not more devs out there using this already.

Not a big fan of answering my own questions, but since the comments degraded into a discussion about speed and no one else has answered yet, I might as well.

Thanks for all who commented.

Upvotes: 4

Related Questions