如何在C#中使用PBKDF2 HMAC SHA-256或SHA-512使用salt和迭代对密码进行哈希?
我想找到一个解决方案或方法,使我能够添加盐和控制迭代次数。本机Rfc2898DeriveBytes基于HMACSHA1。理想情况下,使用SHA-256或SHA-512将使系统经得起未来的考验 这是迄今为止我发现的最好的例子:但当我用SHA-256运行它时,它实际上比用SHA-512运行慢。我使用64k迭代、salt的guid和不同的相同长度密码进行比较 我还发现了这个解决方案:它有完整的源代码。它似乎更加健壮 到目前为止,我无法从它们中获得相同的输出。PWDTK.NET库()似乎是我能找到的唯一一个实现PBKDF2 HMAC SHA-512并允许salt和迭代的实现。我无法找到PBKDF2 HMAC SHA-512要测试的测试向量 我很惊讶没有更多的开发人员已经在使用这个了 我不太喜欢回答我自己的问题,但既然这些评论已经演变成了一场关于速度的讨论,而且还没有人回答,我也可以这样做 感谢所有的评论。我的开源C#on目前提供HMAC SHA1-160和HMAC SHA2-256,以及salt和迭代()。如随附的Windows窗体gui所示,密码和哈希生成的计时内置于库中 目前,我的代码在我的计算机上执行SHA2-256哈希需要0.80秒,迭代次数为65536次。它肯定会更有效,因为我还没有分析过它如何在C#中使用PBKDF2 HMAC SHA-256或SHA-512使用salt和迭代对密码进行哈希?,c#,asp.net,encryption,C#,Asp.net,Encryption,我想找到一个解决方案或方法,使我能够添加盐和控制迭代次数。本机Rfc2898DeriveBytes基于HMACSHA1。理想情况下,使用SHA-256或SHA-512将使系统经得起未来的考验 这是迄今为止我发现的最好的例子:但当我用SHA-256运行它时,它实际上比用SHA-512运行慢。我使用64k迭代、salt的guid和不同的相同长度密码进行比较 我还发现了这个解决方案:它有完整的源代码。它似乎更加健壮 到目前为止,我无法从它们中获得相同的输出。PWDTK.NET库()似乎是我能找到的唯一
我的SHA2-256代码生成与所示相同的测试结果。我的库可以使用任意HMAC执行PBKDF2。Salt和迭代是可以控制的。查看CryptSharp.Utility命名空间。它与一个C#Scrypt实现以及其他一些东西一起出现。另一个实现——在我发现像RoadWarrior、Zer和thasiznets这样的其他实现之前就已经完成了 这类似于
Rfc2898DeriveBytes
源于.NET的System.Cryptography.DeriveBytes
。换句话说,用法是一样的——尽管我只实现了我使用的一个构造函数
除此之外,它完全不是基于微软的实现。这也需要一个免责声明-请参阅此答案的底部
它允许一个任意的伪随机函数,这意味着我们可以插入HMAC SHA256或HMAC SHA512——或者像RFC允许的那样,比我更具密码洞察力和勇气的人可以插入任何他们想要的东西。它还使用long
而不是int
来进行迭代计数——只用于疯狂的迭代
/// <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;
}
}
HMAC-SHA512 IPseudoRandomFunction示例(为简洁起见,我使用允许.NET的任何HMAC类的泛型类):
结果。。。这:
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使用来自
- HMAC-SHA512使用来自
Reset()
的简单测试和对GetBytes()
的多次调用运行
几个初步的性能测试表明,它与SHA-1的.NET实现相一致,在ASCII编码中,通过“代码”> GETBYEL(200)< /代码>,对1000次“1000次”/“SaltHealt”转换为字节。有时比内置实现快一点,有时慢一点——在我的旧电脑上,我们谈论的是84秒对83秒。不过,所有这些都是通过
PBKDF2DeriveBytes
的调试构建完成的(因为大部分工作显然是在HMAC中完成的,我们需要更多的迭代或运行来测量实际的差异)
免责声明:
我不是密码天才。如上所述,这尚未经过严格测试。我不能保证。但是,也许,连同其他答案和实现,它可以帮助理解方法论 这是由SecurityDriven.NET的Inferno库提供的 安装Inferno软件包 鉴于SHA-384被用于保护绝密信息,且“其截断设计可有效防御长度扩展攻击”,Inferno推出了SHA-384 存储用户密码:
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。注意,您可以将它们作为二进制文件保存,也可以使用帮助器将它们存储为字符串。请注意,盐是随机创建的
验证用户的登录:
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
实例时使用persistend UserSalt
最近的替代方案是NuGet package,它允许将PBKDF2与SHA-256和SHA-512哈希函数一起使用,这比内置在
Rfc2898DeriveBytes
中的SHA-1更强大。与其他答案中提到的第三方库相比,它的优势在于它是由Microsoft实现的,因此,一旦您已经依赖.NET平台,就不需要对它执行安全审计。有关文档,请访问。SHA对于密码哈希来说太快了。在.Net framework中,包含了PBKDF2实现。这个问题不包括PDKDF2 HMAC、Scrypt或SHA3。不确定为什么标记为重复。@Developr:PBKDFv2、bcrypt或scrypt。我认为人们没有注意到SHA的快速性。SHA是快速的,这就是为什么要使用密钥拉伸算法(如PBKDF2)进行散列。随着GPU速度的提高,您可以加快迭代次数,并在用户登录时重新设置密码。OP并不是问这是否是一个好的解决方案(事实的确如此!!!)仅仅是一个问题
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();
}
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