Password hash using PBKDF2 with HMAC SHA256/ SHA512 in .NET Framework 4.7 and before

Recently I got an requirement from one of my client to generate salted password hash using PBKDF2 and SHA-2 algorithms. But as everyone knows we only have SHA-1 available in .NET framework till now.

PBKDF2 with SHA-2 is now available in .NET framework from .NET Framework 4.7.2 and .NET Core 2.0 onward. Full article here.

Updated on 20th Sept 2018

Because this was an client requirement so I have to accommodate in my project scope and to do this I got help from a nice guy who made this easy for me. While doing search on google I found this article from that guy.

I got the sample code and modified it a bit to make it more useful as per my need. Below I have posted complete class that you can use directly by copying in your project.

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace PBKDF2SHA2
{
    public class PasswordHash
    {
        //new
        public HMAC Algorithm;
        public Byte[] Salt;
        public Int32 IterationCount;
        private readonly int BlockSize;
        private byte[] BufferBytes;
        private int BufferStartIndex = 0;
        private int BufferEndIndex = 0;
        private uint BlockIndex = 1;
        private int iterations = 4096;
        public const int HashSize = 64;
        public const int SaltSize = 32;

        public PasswordHash(HMAC algorithm, string password)
        {
            if (algorithm == null) { throw new ArgumentNullException("algorithm", "Algorithm cannot be null."); }
            //if (salt == null) { throw new ArgumentNullException("salt", "Salt cannot be null."); }
            if (password == null) { throw new ArgumentNullException("password", "Password cannot be null."); }
            this.Algorithm = algorithm;
            this.Algorithm.Key = UTF8Encoding.UTF8.GetBytes(password);
            this.Salt = GetSalt();
            this.IterationCount = iterations;
            this.BlockSize = this.Algorithm.HashSize / 8;
            this.BufferBytes = new byte[this.BlockSize];
        }

        public bool VerifyPassword(HMAC algorithm, string hash)
        {
            string[] hash_parts = hash.Split(new char[] {':'});
            this.Salt = Convert.FromBase64String(hash_parts[2]);

            return hash == GetHash();
        }

        public byte[] GetSalt()
        {
            var cryptoProvider = new RNGCryptoServiceProvider();
            byte[] salt = new byte[SaltSize];
            cryptoProvider.GetBytes(salt);

            return salt;
        }

        public string GetHash()
        {
            return iterations + ":" + Convert.ToBase64String(GetBytes()) + ":" + Convert.ToBase64String(this.Salt);
        }

        public Byte[] GetBytes()
        {
            byte[] result = new byte[HashSize];
            int resultOffset = 0;
            int bufferCount = this.BufferEndIndex - this.BufferStartIndex;

            if (bufferCount > 0)
            { //if there is some data in buffer
                if (HashSize < bufferCount)
                { //if there is enough data in buffer
                    Buffer.BlockCopy(this.BufferBytes, this.BufferStartIndex, result, 0, HashSize);
                    this.BufferStartIndex += HashSize;
                    return result;
                }
                Buffer.BlockCopy(this.BufferBytes, this.BufferStartIndex, result, 0, bufferCount);
                this.BufferStartIndex = this.BufferEndIndex = 0;
                resultOffset += bufferCount;
            }

            while (resultOffset < HashSize)
            {
                int needCount = HashSize - resultOffset;
                this.BufferBytes = this.Func();
                if (needCount > this.BlockSize)
                { //we one (or more) additional passes
                    Buffer.BlockCopy(this.BufferBytes, 0, result, resultOffset, this.BlockSize);
                    resultOffset += this.BlockSize;
                }
                else
                {
                    Buffer.BlockCopy(this.BufferBytes, 0, result, resultOffset, needCount);
                    this.BufferStartIndex = needCount;
                    this.BufferEndIndex = this.BlockSize;
                    return result;
                }
            }
            return result;
        }

        private byte[] Func()
        {
            var hash1Input = new byte[this.Salt.Length + 4];
            Buffer.BlockCopy(this.Salt, 0, hash1Input, 0, this.Salt.Length);
            Buffer.BlockCopy(GetBytesFromInt(this.BlockIndex), 0, hash1Input, this.Salt.Length, 4);
            var hash1 = this.Algorithm.ComputeHash(hash1Input);

            byte[] finalHash = hash1;
            for (int i = 2; i <= this.IterationCount; i++)
            {
                hash1 = this.Algorithm.ComputeHash(hash1, 0, hash1.Length);
                for (int j = 0; j < this.BlockSize; j++)
                {
                    finalHash[j] = (byte)(finalHash[j] ^ hash1[j]);
                }
            }
            if (this.BlockIndex == uint.MaxValue) { throw new InvalidOperationException("Derived key too long."); }
            this.BlockIndex += 1;

            return finalHash;
        }

        private static byte[] GetBytesFromInt(uint i)
        {
            var bytes = BitConverter.GetBytes(i);
            if (BitConverter.IsLittleEndian)
            {
                return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
            }
            else
            {
                return bytes;
            }
        }
    }
}

You can change values for iterations, HashSize and SaltSize as per your need. Use below code to generate hash of any string and verify with existing hash

var password = "YourPasswordHere";
string hash;

using (var hmac = new HMACSHA512())
{
    var ph= new PasswordHash(hmac, password);
    hash = ph.GetHash();
    Console.WriteLine(hash);
}

using (var hmac = new HMACSHA512())
{
    var ph= new PasswordHash(hmac, password);
    Console.WriteLine(ph.VerifyPassword(hmac, hash));
}