ASP.NET身份默认密码哈希,它是如何工作的,它是安全的?

我想知道密码哈希默认实现在MVC 5和ASP.NET身份框架附带的UserManager ,是否足够安全? 如果是的话,如果你能向我解释它是如何工作的?

IPasswordHasher接口如下所示:

public interface IPasswordHasher { string HashPassword(string password); PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword); } 

正如你所看到的,它并不需要盐,但是在这个线程中提到:“ Asp.net身份密码哈希 ”,它实际上是在幕后进行的。 所以我想知道它是如何做到这一点? 这盐是从哪里来的?

我担心的是盐是静态的,使得它不安全。

这是默认实现的工作原理。 它使用随机盐的密钥推导函数来生成哈希。 盐被包括作为KDF的输出的一部分。 因此,每次你“散列”相同的密码,你会得到不同的哈希值。 为了validation散列,输出被分割回盐和其余部分,并且KDF再次以指定盐的密码运行。 如果结果与初始输出的其余部分相匹配,则validation散列。

哈希:

 public static string HashPassword(string password) { byte[] salt; byte[] buffer2; if (password == null) { throw new ArgumentNullException("password"); } using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, 0x10, 0x3e8)) { salt = bytes.Salt; buffer2 = bytes.GetBytes(0x20); } byte[] dst = new byte[0x31]; Buffer.BlockCopy(salt, 0, dst, 1, 0x10); Buffer.BlockCopy(buffer2, 0, dst, 0x11, 0x20); return Convert.ToBase64String(dst); } 

validation:

 public static bool VerifyHashedPassword(string hashedPassword, string password) { byte[] buffer4; if (hashedPassword == null) { return false; } if (password == null) { throw new ArgumentNullException("password"); } byte[] src = Convert.FromBase64String(hashedPassword); if ((src.Length != 0x31) || (src[0] != 0)) { return false; } byte[] dst = new byte[0x10]; Buffer.BlockCopy(src, 1, dst, 0, 0x10); byte[] buffer3 = new byte[0x20]; Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20); using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, dst, 0x3e8)) { buffer4 = bytes.GetBytes(0x20); } return ByteArraysEqual(buffer3, buffer4); } 

因为现在ASP.NET是开源的,你可以在GitHub上findAspNet.Identity 3.0或者Asp.net.Identity 2.0的Codeplex 。

来自评论:

 /* ======================= * HASHED PASSWORD FORMATS * ======================= * * Version 2: * PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations. * (See also: SDL crypto guidelines v5.1, Part III) * Format: { 0x00, salt, subkey } * * Version 3: * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey } * (All UInt32s are stored big-endian.) */ 

对于像我这样的新手来说,这里是用const和一个实际的方法来比较byte []的代码。 我从stackoverflow得到所有这些代码,但定义了常量,所以值可以改变,也

 // 24 = 192 bits private const int SaltByteSize = 24; private const int HashByteSize = 24; private const int HasingIterationsCount = 10101; public static string HashPassword(string password) { // http://stackoverflow.com/questions/19957176/asp-net-identity-password-hashing byte[] salt; byte[] buffer2; if (password == null) { throw new ArgumentNullException("password"); } using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, SaltByteSize, HasingIterationsCount)) { salt = bytes.Salt; buffer2 = bytes.GetBytes(HashByteSize); } byte[] dst = new byte[(SaltByteSize + HashByteSize) + 1]; Buffer.BlockCopy(salt, 0, dst, 1, SaltByteSize); Buffer.BlockCopy(buffer2, 0, dst, SaltByteSize + 1, HashByteSize); return Convert.ToBase64String(dst); } public static bool VerifyHashedPassword(string hashedPassword, string password) { byte[] _passwordHashBytes; int _arrayLen = (SaltByteSize + HashByteSize) + 1; if (hashedPassword == null) { return false; } if (password == null) { throw new ArgumentNullException("password"); } byte[] src = Convert.FromBase64String(hashedPassword); if ((src.Length != _arrayLen) || (src[0] != 0)) { return false; } byte[] _currentSaltBytes = new byte[SaltByteSize]; Buffer.BlockCopy(src, 1, _currentSaltBytes, 0, SaltByteSize); byte[] _currentHashBytes = new byte[HashByteSize]; Buffer.BlockCopy(src, SaltByteSize + 1, _currentHashBytes, 0, HashByteSize); using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, _currentSaltBytes, HasingIterationsCount)) { _passwordHashBytes = bytes.GetBytes(SaltByteSize); } return AreHashesEqual(_currentHashBytes, _passwordHashBytes); } private static bool AreHashesEqual(byte[] firstHash, byte[] secondHash) { int _minHashLength = firstHash.Length <= secondHash.Length ? firstHash.Length : secondHash.Length; var xor = firstHash.Length ^ secondHash.Length; for (int i = 0; i < _minHashLength; i++) xor |= firstHash[i] ^ secondHash[i]; return 0 == xor; } 

在您的自定义ApplicationUserManager中,您将PasswordHasher属性设置为包含上述代码的类的名称。

我明白接受的答案,并已经表决,但认为我倾倒我的外行在这里的答案…

创build一个哈希

  1. 盐是使用生成散列和盐的函数Rfc2898DeriveBytes随机生成的。 Rfc2898DeriveBytes的input是密码,要生成的salt的大小以及执行散列迭代的次数。 https://msdn.microsoft.com/en-us/library/h83s4e12(v=vs.110).aspx
  2. 然后将盐和散列混合在一起(盐首先是散列),然后编码为一个string(所以盐在散列中编码)。 然后将该编码后的散列(其中包含salt和散列)存储(通常)在数据库中对用户。

根据散列检查密码

检查用户input的密码。

  1. 盐是从存储的散列密码中提取的。
  2. salt用于使用Rfc2898DeriveBytes的过载来散列用户input的密码,该过程需要一个盐而不是生成一个。 https://msdn.microsoft.com/en-us/library/yx129kfs(v=vs.110).aspx
  3. 然后比较存储的散列和testing散列。

哈希

在封面之下,使用SHA1哈希函数( https://en.wikipedia.org/wiki/SHA-1 )生成哈希。 这个函数被迭代地调用1000次(在默认的Identity实现中)

为什么这是安全的

  • 随机盐意味着攻击者不能使用预先生成的哈希表来尝试破解密码。 他们需要为每个盐生成一个哈希表。 (假设这里黑客也损害了你的盐)
  • 如果2个密码是相同的,他们将有不同的哈希值。 (这意味着攻击者无法推断出“普通”密码)
  • 迭代调用SHA1 1000次意味着攻击者也需要这样做。 这个想法是,除非他们有时间在超级计算机上,否则他们将没有足够的资源来蛮力强制密码。 这会大大减慢为给定的盐生成哈希表的时间。