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.