我对密码学完全陌生,但正在学习。我从网上的研究中拼凑出许多不同的建议,并制作了自己的类来处理哈希、盐、密钥拉伸以及关联数据的比较/转换。
在研究了内置的.NET 密码学库后,我发现我拥有的仍然只是 SHA-1。但我得出的结论是,这还不错,因为我使用了哈希过程的多次迭代。这是正确的吗?
但是如果我想从更强大的 SHA-512 开始,我该如何在下面的代码中实现它呢?预先感谢。
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
public class CryptoSaltAndHash
{
private string strHash;
private string strSalt;
public const int SaltSizeInBytes = 128;
public const int HashSizeInBytes = 1024;
public const int Iterations = 3000;
public string Hash { get { return strHash; } }
public string Salt { get { return strSalt; } }
public CryptoSaltAndHash(SecureString ThisPassword)
{
byte[] bytesSalt = new byte[SaltSizeInBytes];
using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(bytesSalt);
}
strSalt = Convert.ToBase64String(bytesSalt);
strHash = ComputeHash(strSalt, ThisPassword);
}
public static string ComputeHash(string ThisSalt, SecureString ThisPassword)
{
byte[] bytesSalt = Convert.FromBase64String(ThisSalt);
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(
convertSecureStringToString(ThisPassword), bytesSalt, Iterations);
using (pbkdf2)
{
return Convert.ToBase64String(pbkdf2.GetBytes(HashSizeInBytes));
}
}
public static bool Verify(string ThisSalt, string ThisHash, SecureString ThisPassword)
{
if (slowEquals(getBytes(ThisHash), getBytes(ComputeHash(ThisSalt, ThisPassword))))
{
return true;
}
return false;
}
private static string convertSecureStringToString(SecureString MySecureString)
{
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.SecureStringToGlobalAllocUnicode(MySecureString);
return Marshal.PtrToStringUni(ptr);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(ptr);
}
}
private static bool slowEquals(byte[] A, byte[] B)
{
int intDiff = A.Length ^ B.Length;
for (int i = 0; i < A.Length && i < B.Length; i++)
{
intDiff |= A[i] ^ B[i];
}
return intDiff == 0;
}
private static byte[] getBytes(string MyString)
{
byte[] b = new byte[MyString.Length * sizeof(char)];
System.Buffer.BlockCopy(MyString.ToCharArray(), 0, b, 0, b.Length);
return b;
}
}
注释:我参考了https://crackstation.net/hashing-security.htm的很多实践。 SlowEquals 比较方法是通过防止分支来标准化执行时间。 SecureString 的用途是在此类与 Web 应用程序中的其他类和页面之间传递密码的加密形式。虽然该网站将通过 HTTPS 进行传输,但在合理的情况下,多加努力以确保尽可能安全总是好的。
在我的代码中,我将密钥字符串设置为 128 字节(尽管有时会变大,这很好),哈希大小设置为 1KB,迭代次数设置为 3,000。它比典型的 64 字节盐、512 字节哈希和 1,000 或 2,000 次迭代稍大,但登录速度和应用程序性能的优先级极低。
想法?
大于本机大小(SHA-1 为 20 字节)的哈希大小会降低防御者的性能,但不会降低攻击者的性能。由于这意味着您可以承担更少的迭代,因此它实际上削弱了安全性。
例如,以与 3000 次迭代的 1024 字节哈希相同的成本,您可以负担得起 156000 次迭代的 20 字节哈希,这比破解成本高出 52 倍。
要使用 SHA-2,您需要完全不同的 PBKDF2 实现,.net 中包含的实现被硬编码为使用 SHA-1。
如果您费心使用第三方库,我宁愿使用 bcrypt 库,因为它对于基于 GPU 的攻击者来说要强大得多。
您的 API 使用起来很尴尬,因为您将盐管理推给调用者,而不是在
Create
/Verify
函数中处理它。使用
SecureString
然后将其转换为String
是愚蠢的。这抵消了首先使用 SecureString
的全部意义。
就我个人而言,我不会在典型的应用程序中使用
SecureString
。只有将其与广泛的全栈安全审查结合起来才有价值,该审查会检查密码是否永远不会存储在 String
中,并且一旦不再需要,总是从可变存储中删除。我不会将密码/盐存储在实例变量中。只需将它们保留在相关功能的本地即可。我只会将配置存储在实例变量中(例如迭代计数)。
虽然 SHA-1 在密码学上被削弱,但攻击会产生冲突。由于密码哈希冲突无关紧要,您首先关心的是原像攻击。 SHA-1 在这方面仍然相当强大。
SHA-512 的主要优点不是它的加密性更强(尽管确实如此),而是 64 位算术使攻击者比防御者付出更多的代价,因为防御者可能会使用提供快速 64 位算术的 64 位 Intel CPU .
如果有人通过搜索遇到这个问题,现在微软提供了 Microsoft.AspNetCore.Cryptography.KeyDerivation NuGet 包,它允许将 PBKDF2 与 SHA-256 和 SHA-512 哈希函数一起使用。文档可在 learn.microsoft.com 获取。
回答问题:从“SecurityDriven.NET”书中下载免费代码示例。找到采用 HMAC 工厂的 PBKDF2 类。 HMACSHA512 工厂可用等。
由于您是密码学新手,我也强烈建议您阅读这本书(例如,充分理解 CodesInChaos 提出的观点)。
对于那些通过搜索找到此内容的人,答案是
Rfc2898DeriveBytes
构造函数将哈希算法作为参数,而 HashAlgorithmName.SHA512
是一个受支持的选项。 所以,这有效:
var pbkdf2 = new Rfc2898DeriveBytes(passwordBytes, bytesSalt, 1000000, HashAlgorithmName.SHA512);