另一种实现方式 - 在我发现像RoadWarrior、Zer和thasiznets这样的其他实现之前。
这个类似于Rfc2898DeriveBytes
,源自.NET的System.Cryptography.DeriveBytes
。换句话说,使用方式是相同的 - 尽管我只实现了我使用的一个构造函数。
除了这个血统,它完全不基于Microsoft的实现。这也需要一个免责声明 - 请参见本答案底部。
它允许任意伪随机函数,这意味着我们可以插入HMAC SHA256或HMAC SHA512 - 或者比我更具加密洞察力和勇气的人可以插入他们想要的任何东西 - 就像RFC允许的那样。它还使用long
而不是int
来进行迭代计数 - 只是为了疯狂的人。
public class PBKDF2DeriveBytes : DeriveBytes
{
private readonly IPseudoRandomFunction prf;
private readonly byte[] salt;
private readonly long iterationCount;
private readonly byte[] saltAndBlockNumber;
private byte[] buffer;
private int bufferIndex;
private int nextBlock;
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;
saltAndBlockNumber = new byte[salt.Length + 4];
Buffer.BlockCopy(salt, 0, saltAndBlockNumber, 0, salt.Length);
Reset();
}
public override byte[] GetBytes(int keyLength)
{
var result = new byte[keyLength];
int resultIndex = 0;
if (buffer != null && bufferIndex > 0)
{
int bufferRemaining = prf.HashSize - bufferIndex;
int bytesFromBuffer = Math.Min(bufferRemaining, keyLength);
if (bytesFromBuffer > 0)
{
Buffer.BlockCopy(buffer, bufferIndex, result, 0,
bytesFromBuffer);
bufferIndex += bytesFromBuffer;
resultIndex += bytesFromBuffer;
}
}
if (resultIndex < keyLength)
{
ComputeBlocks(result, resultIndex);
if (bufferIndex == prf.HashSize)
{
bufferIndex = 0;
}
}
return result;
}
public sealed override void Reset()
{
buffer = null;
bufferIndex = 0;
nextBlock = 1;
}
private void ComputeBlocks(byte[] result, int resultIndex)
{
int currentBlock = nextBlock;
while (resultIndex < result.Length)
{
F(currentBlock);
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)
{
Buffer.BlockCopy(
BlockNumberToBytes(currentBlock), 0,
saltAndBlockNumber, salt.Length, 4
);
buffer = prf.Transform(saltAndBlockNumber);
byte[] result = buffer;
for (long iteration = 2; iteration <= iterationCount; iteration++)
{
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);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(result);
}
return result;
}
}
IPseudoRandomFunction
被声明为:
public interface IPseudoRandomFunction : IDisposable
{
int HashSize { get; }
byte[] Transform(byte[] input);
}
一个HMAC-SHA512 IPseudoRandomFunction的例子(为了简洁起见 - 我使用了一个通用类,允许任何.NET的HMAC类):
public class HMACSHA512PseudoRandomFunction : IPseudoRandomFunction
{
private HMAC hmac;
private bool disposed;
public HmacPseudoRandomFunction(byte[] input)
{
hmac = new HMACSHA512(input);
}
public int HashSize
{
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;
}
}
}
结果... 这样:
using (var prf = new HMACSHA512PseudoRandomFunction(input))
{
using (var hash = new PBKDF2DeriveBytes(prf, salt, 1000))
{
hash.GetBytes(32);
}
}
"
...是这个的HMAC-SHA512等效物:
"
using (var hash = new Rfc2898DeriveBytes(input, salt, 1000))
{
hash.GetBytes(32);
}
测试
PBKDF2DeriveBytes类已经进行了测试,包括:
还对Reset()
和多次调用GetBytes()
进行了简单测试。
几项初步性能测试表明,在将"pass"/"saltSALT"转换为ASCII编码的字节并使用
GetBytes(200)
进行1000次迭代的情况下,它与.NET实现的SHA-1大致相同。有时比内置实现快一点,有时慢一点 - 在我的古老电脑上大约是84秒对83秒。然而,所有这些都是在调试构建的
PBKDF2DeriveBytes
中完成的(由于大部分工作显然是在HMAC中完成的,我们需要更多的迭代或运行来测量实际差异)。
免责声明:
我不是密码学天才。如上所示,这还没有经过严格测试。我不做任何保证。但也许,除了其他答案和实现之外,它可以帮助理解方法论。
Rfc2898DeriveBytes
类中使用。请使用该实现。 - SLaks