【问题标题】:Hash and salt passwords in C#C# 中的哈希和盐密码
【发布时间】:2011-01-09 10:53:16
【问题描述】:

我刚刚浏览了 DavidHayden 在Hashing User Passwords 上的一篇文章。

我真的无法得到他想要达到的目标。

这是他的代码:

private static string CreateSalt(int size)
{
    //Generate a cryptographic random number.
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] buff = new byte[size];
    rng.GetBytes(buff);

    // Return a Base64 string representation of the random number.
    return Convert.ToBase64String(buff);
}

private static string CreatePasswordHash(string pwd, string salt)
{
    string saltAndPwd = String.Concat(pwd, salt);
    string hashedPwd =
        FormsAuthentication.HashPasswordForStoringInConfigFile(
        saltAndPwd, "sha1");
    return hashedPwd;
}

还有其他 C# 方法可以对密码进行哈希处理并添加盐吗?

【问题讨论】:

  • 这里是一个使用盐进行哈希处理的库encrypto.codeplex.com
  • 第一种生成盐的方法中的size应该传入什么?
  • 链接已损坏。
  • @ShaneLeBlanc 您应该至少与 has 函数输出一样多的位。 SHA1 不是加密级别的,所以你至少应该使用SHA256,它输出 256 位或 32 字节。但是,256 位不容易转换为 base 64,因为每个 base64 字符编码 6 位,并且 256 不能完全被 6 整除。所以你需要一个公分母 6(对于 base64)和 8(对于一个字节中的位)超过 256 位,即 264 位或 33 字节。 TLDR:使用 33。

标签: c# hash passwords salt


【解决方案1】:

Salt 用于为哈希增加额外的复杂性,使其更难暴力破解。

来自article on Sitepoint

黑客仍然可以执行 所谓的字典攻击。 恶意方可能会 通过采取字典攻击,对于 例如,他们有 100,000 个密码 知道人们经常使用(例如城市 姓名、运动队等),对它们进行哈希处理, 然后比较每个条目 字典中的每一行 数据库表。如果黑客发现 比赛,宾果!他们有你的密码。 然而,为了解决这个问题,我们 只需要加盐即可。

要对哈希进行加盐,我们只需提出 一串看起来随机的文本, 将其与密码连接起来 由用户提供,然后对两者进行哈希处理 随机生成的字符串和 密码一起作为一个值。我们 然后保存哈希和盐 作为用户中的单独字段 表。

在这种情况下,不仅 黑客需要猜测密码, 他们还得猜盐。 在明文中添加盐可以改善 安全性:现在,如果黑客尝试 字典攻击,他必须散列他的 100,000 个条目,每个条目的盐 用户行。虽然还是 可能,黑客攻击的机会 成功率急剧下降。

.NET 中没有自动执行此操作的方法,因此您将使用上述解决方案。

【讨论】:

  • 盐用于防御彩虹表之类的东西。为了防御字典攻击,像任何好的 KDF 一样需要一个工作因子(也称为密钥拉伸):en.wikipedia.org/wiki/Key_stretching
【解决方案2】:

实际上这有点奇怪,使用字符串转换 - 成员资格提供程序将它们放入配置文件中。哈希和盐是二进制 blob,除非要将它们放入文本文件,否则无需将它们转换为字符串。

在我的书中,Beginning ASP.NET Security,(哦,终于有了一个拉皮条的借口)我做了以下事情

static byte[] GenerateSaltedHash(byte[] plainText, byte[] salt)
{
  HashAlgorithm algorithm = new SHA256Managed();

  byte[] plainTextWithSaltBytes = 
    new byte[plainText.Length + salt.Length];

  for (int i = 0; i < plainText.Length; i++)
  {
    plainTextWithSaltBytes[i] = plainText[i];
  }
  for (int i = 0; i < salt.Length; i++)
  {
    plainTextWithSaltBytes[plainText.Length + i] = salt[i];
  }

  return algorithm.ComputeHash(plainTextWithSaltBytes);            
}

盐生成是问题中的示例。您可以使用Encoding.UTF8.GetBytes(string) 将文本转换为字节数组。如果您必须将哈希转换为其字符串表示,您可以使用Convert.ToBase64StringConvert.FromBase64String 将其转换回来。

您应该注意,您不能在字节数组上使用相等运算符,它会检查引用,因此您应该简单地遍历两个数组以检查每个字节

public static bool CompareByteArrays(byte[] array1, byte[] array2)
{
  if (array1.Length != array2.Length)
  {
    return false;
  }

  for (int i = 0; i < array1.Length; i++)
  {
    if (array1[i] != array2[i])
    {
      return false;
    }
  }

  return true;
}

始终为每个密码使用新盐。盐不必保密,可以与哈希本身一起存储。

【讨论】:

  • 感谢您的建议 - 真的帮助我入门。我还看到了这个链接 dijksterhuis.org/creating-salted-hash-values-in-c>,我发现这是很好的实用建议,并且反映了这篇文章中所说的大部分内容
  • 用于 CompareByteArrays 的漂亮 LINQ 语句重构 return array1.Length == array2.Length &amp;&amp; !array1.Where((t, i) =&gt; t != array2[i]).Any();
  • @Brettski 从技术上讲,是的,但是为每个用户提供 唯一 盐会呈现彩虹表(通常被认为是破解散列的最有效方法密码)实际上没用。 This is a quick oveview 对如何安全地存储密码,以及为什么/如何工作进行了深入但不是压倒性的概述。
  • @hunter :您应该添加一个 .ToList() 以使其恒定时间。例如:返回 array1.Length == array2.Length && !array1.Where((t, i) => t != array2[i]).ToList().Any();否则,LINQ 将在发现一个不相等的字节后立即返回。
  • -1 用于使用快速散列函数。使用慢速结构,如 PBKDF2、bcrypt 或 scrypt。
【解决方案3】:

blowdart 所说的,但代码少了一点。使用 Linq 或 CopyTo 连接数组。

public static byte[] Hash(string value, byte[] salt)
{
    return Hash(Encoding.UTF8.GetBytes(value), salt);
}

public static byte[] Hash(byte[] value, byte[] salt)
{
    byte[] saltedValue = value.Concat(salt).ToArray();
    // Alternatively use CopyTo.
    //var saltedValue = new byte[value.Length + salt.Length];
    //value.CopyTo(saltedValue, 0);
    //salt.CopyTo(saltedValue, value.Length);

    return new SHA256Managed().ComputeHash(saltedValue);
}

Linq 也有一种比较字节数组的简单方法。

public bool ConfirmPassword(string password)
{
    byte[] passwordHash = Hash(password, _passwordSalt);

    return _passwordHash.SequenceEqual(passwordHash);
}

然而,在实施任何这些之前,请查看this post。对于密码散列,您可能需要一种慢速散列算法,而不是快速散列算法。

为此,Rfc2898DeriveBytes 类很慢(并且可以变得更慢),并且可以回答原始问题的第二部分,因为它可以接受密码和盐并返回哈希值。有关更多信息,请参阅this question。注意,Stack Exchange is using Rfc2898DeriveBytes 用于密码哈希(源代码here)。

【讨论】:

  • @MushinNoShin SHA256 是一个快速哈希。密码散列需要慢速散列,如 PBKDF2、bcrypt 或 scrypt。有关详细信息,请参阅 security.se 上的How to securely hash passwords?
【解决方案4】:

我一直在读到像 SHA256 这样的散列函数并不是真正用于存储密码的: https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes

取而代之的是自适应密钥派生函数,如 PBKDF2、bcrypt 或 scrypt。这是 Microsoft 在其 Microsoft.AspNet.Identity 库中为 PasswordHasher 编写的基于 PBKDF2 的代码:

/* =======================
 * HASHED PASSWORD FORMATS
 * =======================
 * 
 * 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.)
 */

public string HashPassword(string password)
{
    var prf = KeyDerivationPrf.HMACSHA256;
    var rng = RandomNumberGenerator.Create();
    const int iterCount = 10000;
    const int saltSize = 128 / 8;
    const int numBytesRequested = 256 / 8;

    // Produce a version 3 (see comment above) text hash.
    var salt = new byte[saltSize];
    rng.GetBytes(salt);
    var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);

    var outputBytes = new byte[13 + salt.Length + subkey.Length];
    outputBytes[0] = 0x01; // format marker
    WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
    WriteNetworkByteOrder(outputBytes, 5, iterCount);
    WriteNetworkByteOrder(outputBytes, 9, saltSize);
    Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
    Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
    return Convert.ToBase64String(outputBytes);
}

public bool VerifyHashedPassword(string hashedPassword, string providedPassword)
{
    var decodedHashedPassword = Convert.FromBase64String(hashedPassword);

    // Wrong version
    if (decodedHashedPassword[0] != 0x01)
        return false;

    // Read header information
    var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);
    var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);
    var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);

    // Read the salt: must be >= 128 bits
    if (saltLength < 128 / 8)
    {
        return false;
    }
    var salt = new byte[saltLength];
    Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);

    // Read the subkey (the rest of the payload): must be >= 128 bits
    var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
    if (subkeyLength < 128 / 8)
    {
        return false;
    }
    var expectedSubkey = new byte[subkeyLength];
    Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);

    // Hash the incoming password and verify it
    var actualSubkey = KeyDerivation.Pbkdf2(providedPassword, salt, prf, iterCount, subkeyLength);
    return actualSubkey.SequenceEqual(expectedSubkey);
}

private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
{
    buffer[offset + 0] = (byte)(value >> 24);
    buffer[offset + 1] = (byte)(value >> 16);
    buffer[offset + 2] = (byte)(value >> 8);
    buffer[offset + 3] = (byte)(value >> 0);
}

private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
{
    return ((uint)(buffer[offset + 0]) << 24)
        | ((uint)(buffer[offset + 1]) << 16)
        | ((uint)(buffer[offset + 2]) << 8)
        | ((uint)(buffer[offset + 3]));
}

请注意,这需要安装 Microsoft.AspNetCore.Cryptography.KeyDerivation nuget 包,该包需要 .NET Standard 2.0(.NET 4.6.1 或更高版本)。对于早期版本的 .NET,请参阅 Microsoft 的 System.Web.Helpers 库中的 Crypto 类。

2015 年 11 月更新
更新了使用来自不同 Microsoft 库的实现的答案,该库使用 PBKDF2-HMAC-SHA256 散列而不是 PBKDF2-HMAC-SHA1(如果 iterCount 足够高,请注意 PBKDF2-HMAC-SHA1 为 still secure)。您可以查看复制简化代码的source,因为它实际上处理了从先前答案实现的验证和升级哈希,如果您将来需要增加 iterCount,这很有用。

【讨论】:

  • 请注意,可能值得将 PBKDF2IterCount 增加到更高的数字,请参阅security.stackexchange.com/q/3959 了解更多信息。
  • 1) 将 PBKDF2SubkeyLength 减少到 20 个字节。这就是 f SHA1 的自然大小,并且将其增大到超过该值会减慢防御者的速度,而不会减慢攻击者的速度。 2)我建议增加迭代次数。我推荐 10k 到 100k,具体取决于您的性能预算。 3) 恒定的时间比较也没有什么坏处,但没有太大的实际影响。
  • KeyDerivationPrf、KeyDerivation 和 BlockCopy 未定义,它们的类是什么?
  • @mrbengi 您是否安装了提到的 Microsoft.AspNet.Cryptography.KeyDerivation nuget 包?如果这不合适,here 是不需要 nuget 包的版本。 Buffer.BlockCopy 应该存在,它是 System 的一部分。
  • nuget 包现在是 Microsoft.AspNetCore.Cryptography.KeyDerivation。
【解决方案5】:

呸,这样更好! http://sourceforge.net/projects/pwdtknet/ 更好,因为.....它执行 Key Stretching 并使用 HMACSHA512 :)

【讨论】:

    【解决方案6】:

    这就是我的做法。我创建哈希并使用ProtectedData api 存储它:

        public static string GenerateKeyHash(string Password)
        {
            if (string.IsNullOrEmpty(Password)) return null;
            if (Password.Length < 1) return null;
    
            byte[] salt = new byte[20];
            byte[] key = new byte[20];
            byte[] ret = new byte[40];
    
            try
            {
                using (RNGCryptoServiceProvider randomBytes = new RNGCryptoServiceProvider())
                {
                    randomBytes.GetBytes(salt);
    
                    using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
                    {
                        key = hashBytes.GetBytes(20);
                        Buffer.BlockCopy(salt, 0, ret, 0, 20);
                        Buffer.BlockCopy(key, 0, ret, 20, 20);
                    }
                }
                // returns salt/key pair
                return Convert.ToBase64String(ret);
            }
            finally
            {
                if (salt != null)
                    Array.Clear(salt, 0, salt.Length);
                if (key != null)
                    Array.Clear(key, 0, key.Length);
                if (ret != null)
                    Array.Clear(ret, 0, ret.Length);
            } 
        }
    
        public static bool ComparePasswords(string PasswordHash, string Password)
        {
            if (string.IsNullOrEmpty(PasswordHash) || string.IsNullOrEmpty(Password)) return false;
            if (PasswordHash.Length < 40 || Password.Length < 1) return false;
    
            byte[] salt = new byte[20];
            byte[] key = new byte[20];
            byte[] hash = Convert.FromBase64String(PasswordHash);
    
            try
            {
                Buffer.BlockCopy(hash, 0, salt, 0, 20);
                Buffer.BlockCopy(hash, 20, key, 0, 20);
    
                using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
                {
                    byte[] newKey = hashBytes.GetBytes(20);
    
                    if (newKey != null)
                        if (newKey.SequenceEqual(key))
                            return true;
                }
                return false;
            }
            finally
            {
                if (salt != null)
                    Array.Clear(salt, 0, salt.Length);
                if (key != null)
                    Array.Clear(key, 0, key.Length);
                if (hash != null)
                    Array.Clear(hash, 0, hash.Length);
            }
        }
    
        public static byte[] DecryptData(string Data, byte[] Salt)
        {
            if (string.IsNullOrEmpty(Data)) return null;
    
            byte[] btData = Convert.FromBase64String(Data);
    
            try
            {
                return ProtectedData.Unprotect(btData, Salt, DataProtectionScope.CurrentUser);
            }
            finally
            {
                if (btData != null)
                    Array.Clear(btData, 0, btData.Length);
            }
        }
    
        public static string EncryptData(byte[] Data, byte[] Salt)
        {
            if (Data == null) return null;
            if (Data.Length < 1) return null;
    
            byte[] buffer = new byte[Data.Length];
    
            try
            {
                Buffer.BlockCopy(Data, 0, buffer, 0, Data.Length);
                return System.Convert.ToBase64String(ProtectedData.Protect(buffer, Salt, DataProtectionScope.CurrentUser));
            }
            finally
            {
                if (buffer != null)
                    Array.Clear(buffer, 0, buffer.Length);
            }
        }
    

    【讨论】:

    • 如何在保存和稍后比较时调用它?
    【解决方案7】:
    create proc [dbo].[hash_pass] @family nvarchar(50), @username nvarchar(50), @pass nvarchar(Max),``` @semat nvarchar(50), @tell nvarchar(50)
    
    as insert into tbl_karbar values (@family,@username,(select HASHBYTES('SHA1' ,@pass)),@semat,@tell)
    

    【讨论】:

      【解决方案8】:

      在回答原始问题的这一部分“是否有任何其他 C# 方法用于散列密码”您可以使用 ASP.NET Identity v3.0 https://www.nuget.org/packages/Microsoft.AspNet.Identity.EntityFramework/3.0.0-rc1-final

      来实现这一点
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Threading.Tasks;
      using Microsoft.AspNet.Identity;
      using System.Security.Principal;
      
      namespace HashTest{
      
      
          class Program
          {
              static void Main(string[] args)
              {
      
                  WindowsIdentity wi = WindowsIdentity.GetCurrent();
      
                  var ph = new PasswordHasher<WindowsIdentity>();
      
                  Console.WriteLine(ph.HashPassword(wi,"test"));
      
                  Console.WriteLine(ph.VerifyHashedPassword(wi,"AQAAAAEAACcQAAAAEA5S5X7dmbx/NzTk6ixCX+bi8zbKqBUjBhID3Dg1teh+TRZMkAy3CZC5yIfbLqwk2A==","test"));
      
              }
          }
      
      
      }
      

      【讨论】:

        【解决方案9】:

        我已经创建了一个库SimpleHashing.Net,以便使用 Microsoft 提供的基本类简化散列过程。普通的 SHA 已经不足以安全存储密码了。

        该库使用来自 Bcrypt 的哈希格式的想法,但由于没有官方的 MS 实现,我更喜欢使用框架中可用的东西(即 PBKDF2),但开箱即用有点太难了。

        这是一个如何使用该库的简单示例:

        ISimpleHash simpleHash = new SimpleHash();
        
        // Creating a user hash, hashedPassword can be stored in a database
        // hashedPassword contains the number of iterations and salt inside it similar to bcrypt format
        string hashedPassword = simpleHash.Compute("Password123");
        
        // Validating user's password by first loading it from database by username
        string storedHash = _repository.GetUserPasswordHash(username);
        isPasswordValid = simpleHash.Verify("Password123", storedHash);
        

        【讨论】:

          【解决方案10】:
           protected void m_GenerateSHA256_Button1_Click(objectSender, EventArgs e)
          {
          string salt =createSalt(10);
          string hashedPassword=GenerateSHA256Hash(m_UserInput_TextBox.Text,Salt);
          m_SaltHash_TextBox.Text=Salt;
           m_SaltSHA256Hash_TextBox.Text=hashedPassword;
          
          }
           public string createSalt(int size)
          {
           var rng= new System.Security.Cyptography.RNGCyptoServiceProvider();
           var buff= new byte[size];
          rng.GetBytes(buff);
           return Convert.ToBase64String(buff);
          }
          
          
           public string GenerateSHA256Hash(string input,string salt)
          {
           byte[]bytes=System.Text.Encoding.UTF8.GetBytes(input+salt);
           new System.Security.Cyptography.SHA256Managed();
           byte[]hash=sha256hashString.ComputedHash(bytes);
           return bytesArrayToHexString(hash);
            }
          

          【讨论】:

          • 其他方法是字符串密码=HashPasswordForStoringInConfigFile(TextBox1.Text,SHA1)
          【解决方案11】:

          我阅读了所有答案,我认为这些已经足够了,特别是 @Michael 散列缓慢的文章和 @CodesInChaos 好的 cmets,但我决定分享我的代码 sn-p用于可能有用且不需要 [Microsoft.AspNet.Cryptography.KeyDerivation] 的散列/验证。

              private static bool SlowEquals(byte[] a, byte[] b)
                      {
                          uint diff = (uint)a.Length ^ (uint)b.Length;
                          for (int i = 0; i < a.Length && i < b.Length; i++)
                              diff |= (uint)(a[i] ^ b[i]);
                          return diff == 0;
                      }
          
              private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
                      {
                          Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt);
                          pbkdf2.IterationCount = iterations;
                          return pbkdf2.GetBytes(outputBytes);
                      }
          
              private static string CreateHash(string value, int salt_bytes, int hash_bytes, int pbkdf2_iterations)
                      {
                          // Generate a random salt
                          RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider();
                          byte[] salt = new byte[salt_bytes];
                          csprng.GetBytes(salt);
          
                          // Hash the value and encode the parameters
                          byte[] hash = PBKDF2(value, salt, pbkdf2_iterations, hash_bytes);
          
                          //You need to return the salt value too for the validation process
                          return Convert.ToBase64String(hash) + ":" + 
                                 Convert.ToBase64String(hash);
                      }
          
              private static bool ValidateHash(string pureVal, string saltVal, string hashVal, int pbkdf2_iterations)
                      {
                          try
                          {
                              byte[] salt = Convert.FromBase64String(saltVal);
                              byte[] hash = Convert.FromBase64String(hashVal);
          
                              byte[] testHash = PBKDF2(pureVal, salt, pbkdf2_iterations, hash.Length);
                              return SlowEquals(hash, testHash);
                          }
                          catch (Exception ex)
                          {
                              return false;
                          }
                      }
          

          请注意非常重要的SlowEquals函数,最后,我希望这对您有所帮助,请不要犹豫,告诉我更好的方法。

          【讨论】:

          • 与其创建一个繁忙的循环,不如加入一个人为的非繁忙延迟。例如使用任务延迟。这将延迟蛮力尝试,但不会阻塞活动线程。
          • @gburton 感谢您的建议。我会检查的。
          • CreateHash 中有一个错字:您将 Convert.ToBase64String(hash) 连接到自身而不是盐。除此之外,这是一个很好的答案,它几乎解决了 cmets 在其他答案中提出的所有问题。
          【解决方案12】:

          我创建了一个具有以下方法的类:

          1. 创建盐

          2. 哈希输入

          3. 验证输入

            public class CryptographyProcessor
            {
                public string CreateSalt(int size)
                {
                    //Generate a cryptographic random number.
                    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
                    byte[] buff = new byte[size];
                    rng.GetBytes(buff);
                    return Convert.ToBase64String(buff);
                }
            
                public string GenerateHash(string input, string salt)
                { 
                    byte[] bytes = Encoding.UTF8.GetBytes(input + salt);
                    SHA256Managed sHA256ManagedString = new SHA256Managed();
                    byte[] hash = sHA256ManagedString.ComputeHash(bytes);
                    return Convert.ToBase64String(hash);
                }
            
                public bool AreEqual(string plainTextInput, string hashedInput, string salt)
                {
                    string newHashedPin = GenerateHash(plainTextInput, salt);
                    return newHashedPin.Equals(hashedInput); 
                }
            }
            

          【讨论】:

          • Alegbe 我试过了,但它会为相同的输入生成两个不同的 has 值。
          【解决方案13】:

          使用来自 Microsoft 的 System.Web.Helpers.Crypto NuGet 包。 它会自动将盐添加到哈希中。

          您可以像这样散列密码:var hash = Crypto.HashPassword("foo");

          您验证这样的密码:var verified = Crypto.VerifyHashedPassword(hash, "foo");

          【讨论】:

            【解决方案14】:

            如果您不使用 asp.net 或 .net core,在 >= .Net Standard 2.0 项目中也有一个简单的方法。

            首先您可以设置所需的哈希大小、盐和与哈希生成持续时间相关的迭代次数:

            private const int SaltSize = 32;
            private const int HashSize = 32;
            private const int IterationCount = 10000;
            

            要生成密码哈希和盐,您可以使用以下内容:

            public static string GeneratePasswordHash(string password, out string salt)
            {
                using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize))
                {
                    rfc2898DeriveBytes.IterationCount = IterationCount;
                    byte[] hashData = rfc2898DeriveBytes.GetBytes(HashSize);
                    byte[] saltData = rfc2898DeriveBytes.Salt;
                    salt = Convert.ToBase64String(saltData);
                    return Convert.ToBase64String(hashData);
                }
            }
            

            要验证用户输入的密码是否有效,您可以检查数据库中的值:

            public static bool VerifyPassword(string password, string passwordHash, string salt)
            {
                using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize))
                {
                    rfc2898DeriveBytes.IterationCount = IterationCount;
                    rfc2898DeriveBytes.Salt = Convert.FromBase64String(salt);
                    byte[] hashData = rfc2898DeriveBytes.GetBytes(HashSize);
                    return Convert.ToBase64String(hashData) == passwordHash;
                }
            }
            

            下面的单元测试展示了用法:

            string password = "MySecret";
            
            string passwordHash = PasswordHasher.GeneratePasswordHash(password, out string salt);
            
            Assert.True(PasswordHasher.VerifyPassword(password, passwordHash, salt));
            Assert.False(PasswordHasher.VerifyPassword(password.ToUpper(), passwordHash, salt));
            

            Microsoft Rfc2898DeriveBytes Source

            【讨论】:

              【解决方案15】:

              原来的问题已经有了很好的答案,但我想补充一点,“SequenceEqual”可能会导致定时攻击。

              检查(字节)序列的正常方法是相同的,是将顺序中的每个字节与第二个字节进行比较。第一个无序的将停止比较并返回“false”。

              byte[] hash1 = ...
              byte[] hash2 = ...
              // can be exploited with a timing attack
              bool equals = hash1.SequenceEqual(hash2);
              

              这样,攻击者需要 256 个字符串以及每个可能的起始字节。他针对该机制运行每个字符串,而获得结果所需时间最长的字符串是具有正确第一个字节的字符串。然后可以在下一个字节上以类似的方式继续攻击......等等。

              我发现here 是一种更好的方法,并且有很好的解释。

              [MethodImpl(MethodImplOptions.NoOptimization)]
              private static bool slowEquals(byte[] a, byte[] b)
              {
                  int diff = a.Length ^ b.Length;
              
                  for (int i = 0; i < a.Length && i < b.Length; i++)
                      diff |= a[i] ^ b[i];
              
                  return diff == 0;
              }
              

              【讨论】:

              • 比较字节所花费的时间是整个操作的极小部分。特别是在所有需要获取密码的网络请求的情况下。即使在非常罕见的情况下,操作如此一致以至于这甚至是可能的,它也需要大量和大量的尝试。很多很多。在这一点上,人们猜测和检查的风险比使用这样的东西要高得多(因此为什么你无论如何都需要限制密码请求。这不是一个真正的问题..
              【解决方案16】:
              public static class Salt
              {
                  public static string GenerateHash(string input)
                  {
                      byte[] salt = new byte[128 / 8];
                      using (var rngCsp = new RNGCryptoServiceProvider())
                      {
                          rngCsp.GetNonZeroBytes(salt);
                      }
                      byte[] bytes = Encoding.UTF8.GetBytes(input);
                      SHA256Managed sHA256ManagedString = new SHA256Managed();
                      byte[] hash = sHA256ManagedString.ComputeHash(bytes);
                      string baseHashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
                          password: input,
                          salt: hash,
                          prf: KeyDerivationPrf.HMACSHA256,
                          iterationCount: 10000,
                          numBytesRequested: 256 / 8)) ;
                      return string.Format($"MYHASHED@",baseHashed,salt);       
                  }
              
                  public static bool AreEqual(string plainTextInput, string hashedInput)
                  {
                      string newHashedPin = GenerateHash(plainTextInput);
                      return newHashedPin.Equals(hashedInput);
                  }
              }
              

              【讨论】:

              • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2014-06-09
              • 1970-01-01
              • 2011-12-06
              • 2010-11-30
              相关资源
              最近更新 更多