'Rfc2898 / PBKDF2 with SHA256 as digest in c#

I want to use Rfc2898 in c# to derive a key. I also need to use SHA256 as Digest for Rfc2898. I found the class Rfc2898DeriveBytes, but it uses SHA-1 and I don't see a way to make it use a different digest.

Is there a way to use Rfc2898 in c# with SHA256 as digest (short of implementing it from scratch)?



Solution 1:[1]

.NET Core has a new implementation of Rfc2898DeriveBytes.

The CoreFX version no longer has the the hashing algorithm hard-coded

The code is available on Github. It was merged to master on March 2017 and has been shipped with .NET Core 2.0.

Solution 2:[2]

For those who need it, .NET Framework 4.7.2 includes an overload of Rfc2898DeriveBytes that allows the hashing algorithm to be specified:

byte[] bytes;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
{
    bytes = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}

The HashAlgorithmName options at the moment are:

  • MD5
  • SHA1
  • SHA256
  • SHA384
  • SHA512

Solution 3:[3]

The BCL Rfc2898DeriveBytes is hardcoded to use sha-1.

KeyDerivation.Pbkdf2 allows for exactly the same output, but it also allows HMAC SHA-256 and HMAC SHA-512. It's faster too; on my machine by around 5 times - and that's good for security, because it allows for more rounds, which makes life for crackers harder (incidentally sha-512 is a lot less gpu-friendly than sha-256 or sha1). And the api is simpler, to boot:

byte[] salt = ...
string password = ...
var rounds = 50000;                       // pick something bearable
var num_bytes_requested = 16;             // 128 bits is fine
var prf = KeyDerivationPrf.HMACSHA512;    // or sha256, or sha1
byte[] hashed = KeyDerivation.Pbkdf2(password, salt, prf, rounds, num_bytes_requested);

It's from the nuget package Microsoft.AspNetCore.Cryptography.KeyDerivation which does not depend on asp.net core; it runs on .net 4.5.1 or .net standard 1.3 or higher.

Solution 4:[4]

You could use Bouncy Castle. The C# specification lists the algorithm "PBEwithHmacSHA-256", which can only be PBKDF2 with SHA-256.

Solution 5:[5]

I know this is an old question, but for anyone that comes across it, you can now use KeyDerivation.Pbkdf2 from the Microsoft.AspNetCore.Cryptography.KeyDerivation nuget package. It is what is used in asp.net core.

Unfortunately it will add a ton of references that aren't really needed. You could just copy the code and paste it into your own project (although you will now have to maintain cryto code which is a PITA)

Solution 6:[6]

For what it's worth, here's a copy of Microsoft's implementation but with SHA-1 replaced with SHA512:

namespace System.Security.Cryptography
{
using System.Globalization;
using System.IO;
using System.Text;

[System.Runtime.InteropServices.ComVisible(true)]
public class Rfc2898DeriveBytes_HMACSHA512 : DeriveBytes
{
    private byte[] m_buffer;
    private byte[] m_salt;
    private HMACSHA512 m_HMACSHA512;  // The pseudo-random generator function used in PBKDF2

    private uint m_iterations;
    private uint m_block;
    private int m_startIndex;
    private int m_endIndex;
    private static RNGCryptoServiceProvider _rng;
    private static RNGCryptoServiceProvider StaticRandomNumberGenerator
    {
        get
        {
            if (_rng == null)
            {
                _rng = new RNGCryptoServiceProvider();
            }
            return _rng;
        }
    }

    private const int BlockSize = 20;

    //
    // public constructors 
    // 

    public Rfc2898DeriveBytes_HMACSHA512(string password, int saltSize) : this(password, saltSize, 1000) { }

    public Rfc2898DeriveBytes_HMACSHA512(string password, int saltSize, int iterations)
    {
        if (saltSize < 0)
            throw new ArgumentOutOfRangeException("saltSize", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));

        byte[] salt = new byte[saltSize];
        StaticRandomNumberGenerator.GetBytes(salt);

        Salt = salt;
        IterationCount = iterations;
        m_HMACSHA512 = new HMACSHA512(new UTF8Encoding(false).GetBytes(password));
        Initialize();
    }

    public Rfc2898DeriveBytes_HMACSHA512(string password, byte[] salt) : this(password, salt, 1000) { }

    public Rfc2898DeriveBytes_HMACSHA512(string password, byte[] salt, int iterations) : this(new UTF8Encoding(false).GetBytes(password), salt, iterations) { }

    public Rfc2898DeriveBytes_HMACSHA512(byte[] password, byte[] salt, int iterations)
    {
        Salt = salt;
        IterationCount = iterations;
        m_HMACSHA512 = new HMACSHA512(password);
        Initialize();
    }

    //
    // public properties 
    //

    public int IterationCount
    {
        get { return (int)m_iterations; }
        set
        {
            if (value <= 0)
                throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
            m_iterations = (uint)value;
            Initialize();
        }
    }

    public byte[] Salt
    {
        get { return (byte[])m_salt.Clone(); }
        set
        {
            if (value == null)
                throw new ArgumentNullException("value");
            if (value.Length < 8)
                throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Cryptography_PasswordDerivedBytes_FewBytesSalt")));
            m_salt = (byte[])value.Clone();
            Initialize();
        }
    }

    // 
    // public methods
    // 

    public override byte[] GetBytes(int cb)
    {
        if (cb <= 0)
            throw new ArgumentOutOfRangeException("cb", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
        byte[] password = new byte[cb];

        int offset = 0;
        int size = m_endIndex - m_startIndex;
        if (size > 0)
        {
            if (cb >= size)
            {
                Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, size);
                m_startIndex = m_endIndex = 0;
                offset += size;
            }
            else
            {
                Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, cb);
                m_startIndex += cb;
                return password;
            }
        }

        //BCLDebug.Assert(m_startIndex == 0 && m_endIndex == 0, "Invalid start or end index in the internal buffer.");

        while (offset < cb)
        {
            byte[] T_block = Func();
            int remainder = cb - offset;
            if (remainder > BlockSize)
            {
                Buffer.InternalBlockCopy(T_block, 0, password, offset, BlockSize);
                offset += BlockSize;
            }
            else
            {
                Buffer.InternalBlockCopy(T_block, 0, password, offset, remainder);
                offset += remainder;
                Buffer.InternalBlockCopy(T_block, remainder, m_buffer, m_startIndex, BlockSize - remainder);
                m_endIndex += (BlockSize - remainder);
                return password;
            }
        }
        return password;
    }

    public override void Reset()
    {
        Initialize();
    }

    private void Initialize()
    {
        if (m_buffer != null)
            Array.Clear(m_buffer, 0, m_buffer.Length);
        m_buffer = new byte[BlockSize];
        m_block = 1;
        m_startIndex = m_endIndex = 0;
    }
    internal static byte[] Int(uint i)
    {
        byte[] b = BitConverter.GetBytes(i);
        byte[] littleEndianBytes = { b[3], b[2], b[1], b[0] };
        return BitConverter.IsLittleEndian ? littleEndianBytes : b;
    }
    // This function is defined as follow : 
    // Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i)
    // where i is the block number. 
    private byte[] Func()
    {
        byte[] INT_block = Int(m_block);

        m_HMACSHA512.TransformBlock(m_salt, 0, m_salt.Length, m_salt, 0);
        m_HMACSHA512.TransformFinalBlock(INT_block, 0, INT_block.Length);
        byte[] temp = m_HMACSHA512.Hash;
        m_HMACSHA512.Initialize();

        byte[] ret = temp;
        for (int i = 2; i <= m_iterations; i++)
        {
            temp = m_HMACSHA512.ComputeHash(temp);
            for (int j = 0; j < BlockSize; j++)
            {
                ret[j] ^= temp[j];
            }
        }

        // increment the block count.
        m_block++;
        return ret;
    }
}
}

In addition to replacing HMACSHA1 with HMACSHA512, you need to add a StaticRandomNumberGenerator property because Utils.StaticRandomNumberGenerator is internal in the microsoft assembly, and you need to add the static byte[] Int(uint i) method because microsoft's Utils.Int is also internal. Other than that, the code works.

Solution 7:[7]

Although this is an old question, since I added reference to this question in my Question Configurable Rfc2898DeriveBytes where I asked whether a generic implementation of the Rfc2898DeriveBytes algorithm was correct.

I have now tested and validated that it generates the exact same hash values if HMACSHA1 is provided for TAlgorithm as the .NET implementation of Rfc2898DeriveBytes

In order to use the class, one must provide the constructor for the HMAC algorithm requiring a byte array as the first argument.

e.g.:

var rfcGenSha1 = new Rfc2898DeriveBytes<HMACSHA1>(b => new HMACSHA1(b), key, ...)
var rfcGenSha256 = new Rfc2898DeriveBytes<HMACSHA256>(b => new HMACSHA256(b), key, ...)

This requires the algorithm to inherit HMAC at this point, I'm believe one might be able to Reduce the restriction to require inheritance from KeyedHashAlgorithm instead of HMAC, as long as the constructor of the algorithm accepts an array of bytes to the constructor.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Bruno Garcia
Solution 2 Tieson T.
Solution 3 Eamon Nerbonne
Solution 4 Maarten Bodewes
Solution 5 F.A.
Solution 6 Matthew
Solution 7 Community