如何对密码进行哈希处理

165

我想在手机上存储密码的哈希值,但是不确定该如何操作。我只能找到加密方法。如何正确地对密码进行哈希处理?

12个回答

379
大部分其他答案在考虑到今天(2012年)的最佳实践时有些过时。
在.NET中本地可用的最强大的密码哈希算法是PBKDF2,由Rfc2898DeriveBytes类表示。 以下代码在这篇文章中是一个独立的类:另一个存储盐值密码哈希的示例。基础知识非常简单,所以我将其拆解如下: 步骤1 使用加密PRNG创建盐值:
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);

步骤2 创建Rfc2898DeriveBytes并获取哈希值:

var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);

第三步:将盐和密码字节组合起来,以备后用。
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);

第四步 将组合的盐+哈希转换为字符串以进行存储
string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });

第五步:将用户输入的密码与存储的密码进行验证。
/* Fetch the stored value */
string savedPasswordHash = DBContext.GetUser(u => u.UserName == user).Password;
/* Extract the bytes */
byte[] hashBytes = Convert.FromBase64String(savedPasswordHash);
/* Get the salt */
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
/* Compute the hash on the password the user entered */
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
/* Compare the results */
for (int i=0; i < 20; i++)
    if (hashBytes[i+16] != hash[i])
        throw new UnauthorizedAccessException();

注意:根据您特定应用的性能要求,值100000可以减小。最小值应该在10000左右。

10
@Daniel,这篇文章主要是讲如何使用比单纯的哈希更安全的方法。如果你只是简单地对密码进行哈希处理,即使加盐,你的用户密码也可能在你告知他们更改密码之前就被泄露(很可能被出售或公开)。使用上述代码可以让攻击者难以攻击,而不是让开发者轻易实现。 - csharptest.net
3
不,每次存储哈希时都需要使用新的盐。这就是为什么它与哈希结合起来用于存储,以便您可以验证密码。 - csharptest.net
10
整个重点在于你本不应该能够做到。 - csharptest.net
13
如果有人正在编写VerifyPassword方法,如果你想使用Linq和更短的布尔调用方式,可以使用以下代码:return hash.SequenceEqual(hashBytes.Skip(_saltSize));请注意,此代码只是对原来的方法进行了简化,并没有改变其含义。 - Jesú Castillo
4
@csharptest.net 你推荐什么样的数组大小?数组大小是否会对安全性产生很大影响?我不太了解哈希/密码学。 - lenny
显示剩余14条评论

103

基于csharptest.net的优秀答案,我编写了一个类:

public static class SecurePasswordHasher
{
    /// <summary>
    /// Size of salt.
    /// </summary>
    private const int SaltSize = 16;

    /// <summary>
    /// Size of hash.
    /// </summary>
    private const int HashSize = 20;

    /// <summary>
    /// Creates a hash from a password.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="iterations">Number of iterations.</param>
    /// <returns>The hash.</returns>
    public static string Hash(string password, int iterations)
    {
        // Create salt
        byte[] salt;
        new RNGCryptoServiceProvider().GetBytes(salt = new byte[SaltSize]);

        // Create hash
        var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
        var hash = pbkdf2.GetBytes(HashSize);

        // Combine salt and hash
        var hashBytes = new byte[SaltSize + HashSize];
        Array.Copy(salt, 0, hashBytes, 0, SaltSize);
        Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);

        // Convert to base64
        var base64Hash = Convert.ToBase64String(hashBytes);

        // Format hash with extra information
        return string.Format("$MYHASH$V1${0}${1}", iterations, base64Hash);
    }

    /// <summary>
    /// Creates a hash from a password with 10000 iterations
    /// </summary>
    /// <param name="password">The password.</param>
    /// <returns>The hash.</returns>
    public static string Hash(string password)
    {
        return Hash(password, 10000);
    }

    /// <summary>
    /// Checks if hash is supported.
    /// </summary>
    /// <param name="hashString">The hash.</param>
    /// <returns>Is supported?</returns>
    public static bool IsHashSupported(string hashString)
    {
        return hashString.Contains("$MYHASH$V1$");
    }

    /// <summary>
    /// Verifies a password against a hash.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="hashedPassword">The hash.</param>
    /// <returns>Could be verified?</returns>
    public static bool Verify(string password, string hashedPassword)
    {
        // Check hash
        if (!IsHashSupported(hashedPassword))
        {
            throw new NotSupportedException("The hashtype is not supported");
        }

        // Extract iteration and Base64 string
        var splittedHashString = hashedPassword.Replace("$MYHASH$V1$", "").Split('$');
        var iterations = int.Parse(splittedHashString[0]);
        var base64Hash = splittedHashString[1];

        // Get hash bytes
        var hashBytes = Convert.FromBase64String(base64Hash);

        // Get salt
        var salt = new byte[SaltSize];
        Array.Copy(hashBytes, 0, salt, 0, SaltSize);

        // Create hash with given salt
        var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
        byte[] hash = pbkdf2.GetBytes(HashSize);

        // Get result
        for (var i = 0; i < HashSize; i++)
        {
            if (hashBytes[i + SaltSize] != hash[i])
            {
                return false;
            }
        }
        return true;
    }
}

使用方法:

// Hash
var hash = SecurePasswordHasher.Hash("mypassword");

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);

一个示例哈希可以是这样的:

$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn

正如您所看到的,我还将迭代次数包含在哈希中以便于使用,并有可能升级此功能,如果需要升级。


如果您对 .net core 有兴趣,我也在 Code Review 上有一个 .net core 版本。


1
只是为了确认,如果您升级哈希引擎,您会增加哈希的V1部分并以此为基础吗? - Mike Cole
1
是的,这就是计划。然后,您将根据 V1V2 决定需要哪种验证方法。 - Christian Gollhardt
3
是的@NelsonSilva。那是因为salt - Christian Gollhardt
6
希望有人能及时发现并修正此帖子中代码的问题(包括我在内的所有复制粘贴者)。 :) - pettys
1
与此同时,您可能想在这里添加更多的迭代 return Hash(password, 10000);,除此之外,到目前为止我还没有遇到任何问题,它已经在生产中使用了约4年 :) 对于较新的.NET Core,我还编写了一个类,位于code review @pettys。 - Christian Gollhardt
显示剩余4条评论

75

更新: 此答案已经严重过时。请使用https://dev59.com/_G855IYBdhLWcg3wuG2W#10402129https://dev59.com/_G855IYBdhLWcg3wuG2W#73125177中的建议。

您可以选择使用

var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);

或者

var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);

要将data作为字节数组获取,您可以使用以下方法:

var data = Encoding.ASCII.GetBytes(password);

并从md5datasha1data返回字符串

var hashedPassword = ASCIIEncoding.GetString(md5data);

12
我推荐您使用SHA1。除非您需要向现有系统保持向后兼容性,否则不建议使用MD5。此外,在您完成使用实现时,请确保将其放置在using语句中或调用Clear() - vcsjones
3
@vcsjones说:我不想在这里发起一场圣战,但是md5对于几乎所有类型的任务都足够好用了。它的漏洞也只针对非常特定的情况,并且几乎需要攻击者对密码学有深入的了解。 - zerkms
4
@zerkms 的意见已被听取,但如果没有向后兼容性的原因,就没有使用MD5哈希算法的必要。“宁可安全也不要遗憾后悔”。 - vcsjones
4
目前没有使用MD5的理由。鉴于计算时间微不足道,除了与现有系统兼容性之外,没有理由使用MD5。即使MD5“足够好”,使用更安全的SHA也没有成本。我相信zerkms知道这一点,这个评论更多是针对提问者的。 - Gerald Davis
12
三大错误:1)ASCII 会默默地降低密码的安全性,尤其是包含不寻常字符的密码;2)普通的 MD5/SHA-1/SHA-2 算法速度很快,但容易被暴力破解;3)需要使用盐来加强密码的强度。建议使用 PBKDF2、bcrypt 或 scrypt 等算法代替原有算法。其中,PBKDF2 算法最为简单,可以通过 Rfc2898DeriveBytes 类实现(不确定 WP7 是否支持该类)。 - CodesInChaos
显示剩余7条评论

31

2022年(.NET 6+)解决方案:

这里的大多数其他答案是几年前编写的,因此没有利用较新版本的.NET中引入的许多更近期的功能。现在可以通过更简单和更少的样板和噪音来实现相同的事情。我提出的解决方案还通过允许您在未来修改设置(例如迭代计数等)而不会实际破坏旧哈希值来提供额外的鲁棒性(例如,如果您使用接受的答案提出的解决方案,那么就会发生这种情况)。

优点:

  • 使用 .NET 6 中引入的新静态Rfc2898DeriveBytes.Pbkdf2() 方法,无需每次实例化和释放对象。

  • 使用 .NET 6 中引入的新RandomNumberGenerator 类及其静态 GetBytes 方法来生成盐值。接受的答案中使用的 RNGCryptoServiceProvider 类已过时is obsolete

  • Verify 方法中使用 CryptographicOperations.FixedTimeEquals 方法(在 .NET Core 2.1 中引入)比手动比较密钥字节(如接受的答案所做的)更好。此外,这还能消除很多嘈杂的样板代码,同时也避免了 timing attacks

  • 出于安全考虑,将默认的 SHA-1 算法替换为 SHA-256 算法作为底层算法,因为后者是一种更强大、可靠的算法。

  • Hash 方法返回的字符串具有以下结构:

    [key]:[salt]:[iterations]:[algorithm]

    这是此解决方案最重要的优势,它意味着我们基本上在最终字符串中“包括有关用于创建哈希的配置的元数据。这使我们可以在将来更改散列器类中的设置(如迭代次数、盐/密钥大小等)而不会破坏使用旧设置创建的先前哈希。例如,接受的答案没有考虑当前配置值来验证哈希。

其他注意事项:

  • 我在返回的哈希字符串中使用了密钥和盐的十六进制表示法。如果您喜欢,也可以使用base64,只需将每个Convert.ToHexStringConvert.FromHexString更改为Convert.ToBase64Convert.FromBase64即可。其余逻辑保持不变。
  • 推荐使用的盐大小通常为64位或更多。我将其设置为128位。
  • 密钥大小通常应与所选算法的自然输出大小相同 - 请参见this comment。在我们的例子中,正如我之前提到的那样,底层算法是SHA-256,其输出大小为256位,这正是我们设置密钥大小的精确值。
  • 如果您计划将其用于存储用户密码,则通常建议使用至少10,000次迭代或更多。我已将默认值设置为50,000,您当然可以根据需要进行更改。

代码如下:

public static class SecretHasher
{
    private const int _saltSize = 16; // 128 bits
    private const int _keySize = 32; // 256 bits
    private const int _iterations = 50000;
    private static readonly HashAlgorithmName _algorithm = HashAlgorithmName.SHA256;

    private const char segmentDelimiter = ':';

    public static string Hash(string input)
    {
        byte[] salt = RandomNumberGenerator.GetBytes(_saltSize);
        byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
            input,
            salt,
            _iterations,
            _algorithm,
            _keySize
        );
        return string.Join(
            segmentDelimiter,
            Convert.ToHexString(hash),
            Convert.ToHexString(salt),
            _iterations,
            _algorithm
        );
    }

    public static bool Verify(string input, string hashString)
    {
        string[] segments = hashString.Split(segmentDelimiter);
        byte[] hash = Convert.FromHexString(segments[0]);
        byte[] salt = Convert.FromHexString(segments[1]);
        int iterations = int.Parse(segments[2]);
        HashAlgorithmName algorithm = new HashAlgorithmName(segments[3]);
        byte[] inputHash = Rfc2898DeriveBytes.Pbkdf2(
            input,
            salt,
            iterations,
            algorithm,
            hash.Length
        );
        return CryptographicOperations.FixedTimeEquals(inputHash, hash);
    }
}

使用方法:

// Hash:
string password = "...";
string hashed = SecretHasher.Hash(password);

// Verify:
string enteredPassword = "...";
bool isPasswordCorrect = SecretHasher.Verify(enteredPassword, hashed);

1
_pdkdf2Algorithm 应该是什么?它是未定义的。 - Scottie
@Scottie,实际上应该是 _algorithm,我已经修复了。 感谢你发现了这个问题。 - aradalvand
4
这应该是2022年的最佳答案。美妙。 - user18270057

18

@csharptest.netChristian Gollhardt的回答非常好,非常感谢。但是,在生产环境中运行这段代码并处理数百万条记录后,我发现存在内存泄漏问题。RNGCryptoServiceProviderRfc2898DeriveBytes类是从IDisposable派生的,但我们没有将它们释放。如果有人需要已释放版本的解决方案,我将在答案中编写我的解决方案。

public static class SecurePasswordHasher
{
    /// <summary>
    /// Size of salt.
    /// </summary>
    private const int SaltSize = 16;

    /// <summary>
    /// Size of hash.
    /// </summary>
    private const int HashSize = 20;

    /// <summary>
    /// Creates a hash from a password.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="iterations">Number of iterations.</param>
    /// <returns>The hash.</returns>
    public static string Hash(string password, int iterations)
    {
        // Create salt
        using (var rng = new RNGCryptoServiceProvider())
        {
            byte[] salt;
            rng.GetBytes(salt = new byte[SaltSize]);
            using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations))
            {
                var hash = pbkdf2.GetBytes(HashSize);
                // Combine salt and hash
                var hashBytes = new byte[SaltSize + HashSize];
                Array.Copy(salt, 0, hashBytes, 0, SaltSize);
                Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
                // Convert to base64
                var base64Hash = Convert.ToBase64String(hashBytes);

                // Format hash with extra information
                return $"$HASH|V1${iterations}${base64Hash}";
            }
        }

    }

    /// <summary>
    /// Creates a hash from a password with 10000 iterations
    /// </summary>
    /// <param name="password">The password.</param>
    /// <returns>The hash.</returns>
    public static string Hash(string password)
    {
        return Hash(password, 10000);
    }

    /// <summary>
    /// Checks if hash is supported.
    /// </summary>
    /// <param name="hashString">The hash.</param>
    /// <returns>Is supported?</returns>
    public static bool IsHashSupported(string hashString)
    {
        return hashString.Contains("HASH|V1$");
    }

    /// <summary>
    /// Verifies a password against a hash.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="hashedPassword">The hash.</param>
    /// <returns>Could be verified?</returns>
    public static bool Verify(string password, string hashedPassword)
    {
        // Check hash
        if (!IsHashSupported(hashedPassword))
        {
            throw new NotSupportedException("The hashtype is not supported");
        }

        // Extract iteration and Base64 string
        var splittedHashString = hashedPassword.Replace("$HASH|V1$", "").Split('$');
        var iterations = int.Parse(splittedHashString[0]);
        var base64Hash = splittedHashString[1];

        // Get hash bytes
        var hashBytes = Convert.FromBase64String(base64Hash);

        // Get salt
        var salt = new byte[SaltSize];
        Array.Copy(hashBytes, 0, salt, 0, SaltSize);

        // Create hash with given salt
        using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations))
        {
            byte[] hash = pbkdf2.GetBytes(HashSize);

            // Get result
            for (var i = 0; i < HashSize; i++)
            {
                if (hashBytes[i + SaltSize] != hash[i])
                {
                    return false;
                }
            }

            return true;
        }

    }
}

使用:

// Hash
var hash = SecurePasswordHasher.Hash("mypassword");

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);

在你的IsHashSupported版本中,似乎丢失了一个“$”符号。 - mlaan

17
在ASP.NET Core中,使用PasswordHasher<TUser>
 • 命名空间:Microsoft.AspNetCore.Identity
 • 程序集:Microsoft.Extensions.Identity.Core.dll (NuGet | Source)

为了对密码进行哈希处理,请使用HashPassword()函数:
var hashedPassword = new PasswordHasher<object?>().HashPassword(null, password);

要验证密码,使用VerifyHashedPassword()

var passwordVerificationResult = new PasswordHasher<object?>().VerifyHashedPassword(null, hashedPassword, password);
switch (passwordVerificationResult)
{
    case PasswordVerificationResult.Failed:
        Console.WriteLine("Password incorrect.");
        break;
    
    case PasswordVerificationResult.Success:
        Console.WriteLine("Password ok.");
        break;
    
    case PasswordVerificationResult.SuccessRehashNeeded:
        Console.WriteLine("Password ok but should be rehashed and updated.");
        break;
    
    default:
        throw new ArgumentOutOfRangeException();
}


优点:

  • 属于.NET平台的一部分。比自己构建加密算法更安全可靠。
  • 可配置的迭代次数和未来兼容性(请参阅PasswordHasherOptions)。
  • 在验证密码时考虑了Timing Attacksource),就像PHPGo所做的那样。

缺点:


1
再添加一个到Cons中: <TUser> 作为虚拟参数。它是 ASP.NET Core Identity 的一部分,这意味着将来可能会有一些依赖于 Identity 框架。 - Klyuch

16

我使用哈希和盐来进行密码加密(与 Asp.Net Membership 使用的相同哈希算法):

private string PasswordSalt
{
   get
   {
      var rng = new RNGCryptoServiceProvider();
      var buff = new byte[32];
      rng.GetBytes(buff);
      return Convert.ToBase64String(buff);
   }
}

private string EncodePassword(string password, string salt)
{
   byte[] bytes = Encoding.Unicode.GetBytes(password);
   byte[] src = Encoding.Unicode.GetBytes(salt);
   byte[] dst = new byte[src.Length + bytes.Length];
   Buffer.BlockCopy(src, 0, dst, 0, src.Length);
   Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
   HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
   byte[] inarray = algorithm.ComputeHash(dst);
   return Convert.ToBase64String(inarray);
}

18
不应使用简单的SHA-1哈希函数,因为它速度过快。应使用缓慢的密钥派生函数,例如PBKDF2、bcrypt或scrypt。 - CodesInChaos

3
  1. 创建一个salt,
  2. 用salt创建hash密码,
  3. 保存hash和salt,
  4. 使用密码和salt解密... 这样开发者就无法解密密码。
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); 
      }
 }

3
我认为使用KeyDerivation.Pbkdf2比Rfc2898DeriveBytes更好。
示例和说明: 在ASP.NET Core中哈希密码
using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
 
public class Program
{
    public static void Main(string[] args)
    {
        Console.Write("Enter a password: ");
        string password = Console.ReadLine();
 
        // generate a 128-bit salt using a secure PRNG
        byte[] salt = new byte[128 / 8];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(salt);
        }
        Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");
 
        // derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)
        string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
            password: password,
            salt: salt,
            prf: KeyDerivationPrf.HMACSHA1,
            iterationCount: 10000,
            numBytesRequested: 256 / 8));
        Console.WriteLine($"Hashed: {hashed}");
    }
}
 
/*
 * SAMPLE OUTPUT
 *
 * Enter a password: Xtw9NMgx
 * Salt: NZsP6NnmfBuYeJrrAKNuVQ==
 * Hashed: /OOoOer10+tGwTRDTrQSoeCxVTFr6dtYly7d0cPxIak=
 */

这是一篇文章的示例代码,它具有最低安全级别。为了加强安全性,我会使用KeyDerivationPrf.HMACSHA256或KeyDerivationPrf.HMACSHA512参数代替KeyDerivationPrf.HMACSHA1。
不要在密码哈希上妥协。有许多数学上可靠的方法来优化密码哈希破解。后果可能是灾难性的。一旦恶意用户可以获取到您的用户密码哈希表,如果算法是弱的或者实现不正确,那么破解密码将相对容易。他有大量的时间(时间x计算机算力)来破解密码。密码哈希应该是密码学上强的,以将“大量时间”转化为“不合理的时间”。
还有一个要点需要补充:哈希验证需要时间(这是好事)。当用户输入错误的用户名时,检查用户名不需要时间。当用户名正确时,我们开始密码验证——这是相对较长的过程。对于黑客来说,很容易了解用户是否存在。确保在用户名错误时不要立即返回答案。毋庸置疑:永远不要提供错误的答案,只需一般性地说“凭据错误”。

1
顺便说一句,之前的回答 https://dev59.com/_G855IYBdhLWcg3wuG2W#57508528 是不正确且有害的。那只是哈希的一个例子,而不是密码哈希。在密钥派生过程中必须进行伪随机函数的迭代。 我无法评论或投票反对(因为我的声望较低)。 请不要错过不正确的答案! - Albert Lyubarsky
你似乎对加密非常了解。如果您能提供加密和验证过程,那将非常友好,这样像我这样技术较差的人也可以使用您的安全提案。我查看了您提供的链接,但不幸的是,它们也没有提供验证方法。 - John Ranger
@John Ranger。
  1. 注册用户。 1.1 输入用户名(或电子邮件)、密码和重复密码字段。 1.2 如果密码==重复密码,则从密码生成哈希值。 1.3. 持久化哈希值和用户名。
  2. 验证。 2.1 输入用户的密码和用户名。 2.2 从中生成哈希值。 2.3 检索存储的哈希值,通过其用户名。 2.4 如果存储的哈希值==当前哈希值-OK,否则NOT OK。 就这些。
- Albert Lyubarsky
如果用户名不存在且没有可检索的内容,请在返回“NOT OK”之前稍作延迟。并且永远不要告诉用户出了什么问题:用户名不存在或存储的哈希值!=当前哈希值,只需一般性错误即可。在N次重试后,考虑将用户锁定K分钟以防止暴力攻击。 - Albert Lyubarsky

1
使用以下类先生成一个盐。每个用户需要有不同的盐,我们可以将其与其他用户属性一起保存在数据库中。 rounds 值决定密码将被哈希的次数。
更多细节请参见: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes.-ctor?view=netcore-3.1#System_Security_Cryptography_Rfc2898DeriveBytes__ctor_System_Byte___System_Byte___System_Int32_
public class HashSaltWithRounds
{
    int saltLength = 32;
    public byte[] GenerateSalt()
    {
        using (var randomNumberGenerator = new RNGCryptoServiceProvider())
        {
            var randomNumber = new byte[saltLength];
            randomNumberGenerator.GetBytes(randomNumber);
            return randomNumber;
        }
    }

    public string HashDataWithRounds(byte[] password, byte[] salt, int rounds)
    {
        using(var rfc2898= new Rfc2898DeriveBytes(password, salt, rounds))
        {
            return Convert.ToBase64String(rfc2898.GetBytes(32));
        }
    }
}

我们可以从控制台应用程序中按如下方式调用它。我使用相同的盐对密码进行了两次哈希处理。
public class Program
{
    public static void Main(string[] args)
    {
        int numberOfIterations = 99;
        var hashFunction = new HashSaltWithRounds();

        string password = "Your Password Here";
        byte[] salt = hashFunction.GenerateSalt();

        var hashedPassword1 = hashFunction.HashDataWithRounds(Encoding.UTF8.GetBytes(password), salt, numberOfIterations);
        var hashedPassword2 = hashFunction.HashDataWithRounds(Encoding.UTF8.GetBytes(password), salt, numberOfIterations);

        Console.WriteLine($"hashedPassword1 :{hashedPassword1}");
        Console.WriteLine($"hashedPassword2 :{hashedPassword2}");
        Console.WriteLine(hashedPassword1.Equals(hashedPassword2));

        Console.ReadLine();

    }
}

Output


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接