如何使用C#中的PBKDF2 HMAC SHA-256或SHA-512在salt和迭代中散列密码?

我想找到一个解决方案或方法,允许我添加盐并控制迭代次数。 原生Rfc2898DeriveBytes基于HMACSHA1。 理想情况下,使用SHA-256或SHA-512将使系统面向未来。

这是我到目前为止找到的最好的例子: http : //jmedved.com/2012/04/pbkdf2-with-sha-256-and-others/但是当我使用SHA-256运行它时实际上比SHA-512。 我使用了64k迭代,一个盐的guid和不同长度的密码进行比较。

我也找到了这个解决方案: http : //sourceforge.net/projects/pwdtknet/ ,它有完整的源代码。 它看起来更强大。

到目前为止,我无法从它们中获得相同的输出。

PWDTK.NET库( http://sourceforge.net/projects/pwdtknet/ )似乎是我能找到的唯一实现PBKDF2 HMAC SHA-512并允许盐和迭代的实现。 我无法找到PBKDF2 HMAC SHA-512的测试向量进行测试。

令我感到惊讶的是,目前还没有更多的开发者使用它。

不是回答我自己的问题的忠实粉丝,但由于评论降级为关于速度的讨论而且还没有其他人回答,我不妨。

感谢所有评论的人。

我的CryptSharp库可以使用任意HMAC进行PBKDF2。 可以控制盐和迭代。 查看CryptSharp.Utility命名空间。 它与C#Scrypt实现以及其他一些东西一起存在。

这是由SecurityDriven.NET的Inferno库提供的。

Install-Package Inferno

Inferno推广SHA-384,因为它被NSA Suite B用于保护绝密信息,“它的截断设计可以有效抵御长度扩展攻击” (1) 。

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

存储用户密码:

 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(); } 

坚持salt和hashedPassword。 请注意,您可以将它们保存为binrary或使用帮助程序将它们存储为字符串。 请注意,盐是随机创建的。

validation用户的登录信息:

 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 

正如您在此处所看到的,该过程几乎完全相同。 关键的区别在于没有使用CryptoRandom ,我们在创建PBKDF2实例时使用持久的UserSalt。

源于GitHub

我在Google Code上的开源C# 密码实用程序库目前使用HMAC SHA1-160和HMAC SHA2-256,以及salt和迭代( PKDBF2 )。 用于密码和哈希生成的计时内置于库中,如随附的Windows Forms gui所示。

我的代码目前在我的机器上花了0.80秒来完成SHA2-256哈希,重复65,536次。 它肯定会更有效率,因为我尚未对其进行分析。

我的SHA2-256代码产生的测试结果与此处显示的相同。

另一个实现 – 从我发现其他像RoadWarrior,Zer和thasiznets之前已经完成了它。

这与Rfc2898DeriveBytes一样,源自.NET的System.Cryptography.DeriveBytes 。 换句话说,用法是相同的 – 虽然我只实现了我使用的一个构造函数。

除了那个血统之外,它根本不是基于微软的实施。 这也需要免责声明 – 请参阅此答案的底部。

它允许任意伪随机函数,这意味着我们可以插入HMAC SHA256或HMAC SHA512 – 或者具有更多加密洞察力和勇气的人可以插入任何他们想要的东西 – 就像RFC允许的那样。 对于迭代次数,它也使用long而不是int – 只适用于疯狂的迭代次数。

 ///  /// More generic version of the built-in Rfc2898DeriveBytes class. This one /// allows an arbitrary Pseudo Random Function, meaning we can use eg /// HMAC SHA256 or HMAC SHA512 rather than the hardcoded HMAC SHA-1 of the /// built-in version. ///  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; ///  /// The Pseudo Random Function to use for calculating the derived key ///  ///  /// The initial salt to use in calculating the derived key ///  ///  /// 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 (eg each year). ///  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(); } ///  /// 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. ///  ///  /// 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. ///  /// The derived key 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; } ///  /// 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. ///  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声明为:

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

一个示例HMAC-SHA512 IPseudoRandomFunction(为简洁起见 - 我使用允许任何.NET的HMAC类的generics类):

 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; } } } 

结果......这个:

 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类

  • 具有随机输入的HMAC-SHA1产生与Microsoft的实现相同的结果
  • HMAC-SHA1使用RFC 6070测试向量。
  • HMAC-SHA256使用来自https://stackoverflow.com/a/5136918/1169696的测试向量
  • HMAC-SHA512使用来自PBKDF2-HMAC-SHA-512测试载体的测试载体

它还运行了Reset()简单测试和对GetBytes()多次调用。

一些初步的性能测试表明它与SHA-1的.NET实现相同,1000次运行1000次迭代,“pass”/“saltSALT”使用GetBytes(200)转换为ASCII编码的字节。 有时比内置的实现快一点,有时慢一点 - 我们在古老的计算机上谈论84和83秒。 所有这些都是通过PBKDF2DeriveBytes的调试构建完成的(因为大部分工作显然是在HMAC中完成的,我们需要更多的迭代或运行来测量实际的差异)。

免责声明

我不是加密天才。 如上所示,这尚未经过严格测试。 我不保证。 但也许,与其他答案和实施一起,它可以帮助理解方法。

最近的替代方案是Microsoft.AspNetCore.Cryptography.KeyDerivation NuGet包,它允许将PBKDF2与SHA-256和SHA-512哈希函数一起使用,这些函数比内置于Rfc2898DeriveBytes SHA-1 Rfc2898DeriveBytes 。 在其他答案中提到的优于第三方库的优势在于它是由Microsoft实现的,因此一旦您已经依赖.NET平台,就不需要对它进行安全审计。 文档可在docs.microsoft.com上获得 。