如何哈希密码

Sko*_*der 111 c# security passwords hash windows-phone-7

嘿,我想在手机上存储密码的哈希,但我不知道该怎么做.我似乎只能找到加密方法.哈希密码的最佳方法是什么?谢谢

csh*_*net 275

这里的大多数其他答案都与今天的最佳实践有些过时.因此,这里是使用PBKDF2/Rfc2898DeriveBytes来存储和验证密码的应用.以下代码在本文中的独立类中:另一个如何存储salted密码哈希的示例.基础知识非常简单,所以在这里分解:

步骤1使用加密PRNG创建salt值:

byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
Run Code Online (Sandbox Code Playgroud)

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

var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
byte[] hash = pbkdf2.GetBytes(20);
Run Code Online (Sandbox Code Playgroud)

步骤3将salt和password字节组合起来供以后使用:

byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
Run Code Online (Sandbox Code Playgroud)

步骤4将组合的salt + hash转换为字符串进行存储

string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });
Run Code Online (Sandbox Code Playgroud)

步骤5根据存储的密码验证用户输入的密码

/* 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, 10000);
byte[] hash = pbkdf2.GetBytes(20);
/* Compare the results */
for (int i=0; i < 20; i++)
    if (hashBytes[i+16] != hash[i])
        throw new UnauthorizedAccessException();
Run Code Online (Sandbox Code Playgroud)

注意:根据特定应用程序的性能要求,可以减少值"10000".最小值应该在1000左右.

  • @CririanJijie整点是你不能想的. (9认同)
  • @Daniel基本上这个帖子是关于使用比单独哈希更安全的东西.如果您只是哈希密码,即使使用salt,您的用户密码也会在您有机会告诉他们更改之前受到损害(并且可能已经出售/发布).使用上面的代码使攻击者很难,对开发人员来说并不容易. (7认同)
  • 如果有人正在使用VerifyPassword方法,如果你想使用Linq和更短的调用布尔值,这将做:return hash.SequenceEqual(hashBytes.Skip(_saltSize)); (7认同)
  • @DatVM不,每次存储哈希值时都要添加新盐。这就是为什么它与哈希一起存储的原因,以便您可以验证密码。 (2认同)
  • @ csharptest.net您推荐什么样的阵列大小?数组的大小是否会在很大程度上影响安全性?我对哈希/密码学不了解 (2认同)

Chr*_*rdt 61

根据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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
Run Code Online (Sandbox Code Playgroud)

示例哈希可以是这样的:

$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我还将哈希中的迭代包含在内以便于使用,如果我们需要升级,还可以升级它.

  • 有了这个代码的所有复制/粘贴(包括我),我希望有人说出来,如果发现问题,帖子会得到修改!:) (3认同)
  • 是的@NelsonSilva.那是因为[盐](https://en.wikipedia.org/wiki/Salt_(cryptography)). (2认同)

zer*_*kms 60

更新:这个答案严重过时了.请使用/sf/answers/728149061/中的建议.

你可以使用

var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);
Run Code Online (Sandbox Code Playgroud)

要么

var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);
Run Code Online (Sandbox Code Playgroud)

要获得data可以使用的字节数组

var data = Encoding.ASCII.GetBytes(password);
Run Code Online (Sandbox Code Playgroud)

并从md5data或返回字符串sha1data

var hashedPassword = ASCIIEncoding.GetString(md5data);
Run Code Online (Sandbox Code Playgroud)

  • 我真的建议使用SHA1.除非您保持与现有系统的向后兼容性,否则MD5是禁止的.此外,确保在完成使用实现后将其放在`using`语句中或在其上调用`Clear()`. (10认同)
  • 三大错误:1)ASCII使用异常字符无声地降级密码2)普通MD5/SHA-1/SHA-2速度很快.3)你需要盐.| 请改用PBKDF2,bcrypt或scrypt.PBKDF2在Rfc2898DeriveBytes类中最简单(不确定WP7上是否存在) (9认同)
  • @zerkms指向,但如果没有向后兼容的理由,则没有理由使用MD5."比抱歉更安全". (4认同)
  • 此时没有理由使用MD5.鉴于计算时间无关紧要,除了与现有系统的兼容性外,没有理由使用MD5.即使MD5"足够好",用户也无需担心更安全的SHA.我相信zerkms知道这个评论更多的是提问者. (4认同)
  • @vcsjones:我不想在这里进行圣战,但是`md5`对于几乎所有类型的任务来说已经足够了.它的漏洞也指非常具体的情况,几乎要求攻击者了解密码学的很多内容. (3认同)
  • 令人惊讶的是没有人说为什么使用MD5是不好的:因为它的速度非常快(比如用5-10个字符串来填充5-10个字符串),所以有可能运行暴力攻击来查找给定md5哈希的密码(比方说10)十亿可能的密码5个字符长=> 10000秒或单个CPU上不到3个小时). (2认同)

Ami*_*adi 35

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

\n

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

\n

优点:

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

    \n
  • \n
  • 使用.NET 6 \xe2\x80\x94 中引入的新RandomNumberGenerator类及其静态方法 \xe2\x80\x94 来生成盐。接受的答案中使用的类GetBytes过时RNGCryptoServiceProvider

    \n
  • \n
  • 使用CryptographicOperations.FixedTimeEquals方法(在 .NET Core 2.1 中引入)来比较方法中的关键字节Verify,而不是像已接受的答案那样手动进行比较 \xe2\x80\x94 。这除了消除大量嘈杂的样板之外,还消除了定时攻击

    \n
  • \n
  • 使用 SHA-256 而不是默认的 SHA-1 作为底层算法,只是为了安全起见,因为后者是一种更强大、更可靠的算法。

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

    \n

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

    \n

    这是该解决方案最重要的优点,这意味着我们基本上包含有关用于在最终字符串中创建哈希的配置的元数据。这允许我们将来更改哈希器类中的设置(例如迭代次数、salt/key 大小等),而不会破坏使用旧设置创建的先前哈希值。例如,这是公认的答案没有考虑到的问题,因为它依赖“当前”配置值来验证哈希值。

    \n
  • \n
\n

其他要点:

\n
    \n
  • 我在返回的哈希字符串中使用密钥和盐的十六进制表示形式。如果您愿意,也可以使用 base64,只需将每次出现的Convert.ToHexString和分别更改Convert.FromHexStringConvert.ToBase64Convert.FromBase64即可。其余逻辑保持完全相同。
  • \n
  • 通常推荐的盐大小为 64 位或更高。我已将其设置为 128 位。
  • \n
  • 密钥大小通常应与所选算法的自然输出大小相同 \xe2\x80\x94 请参阅此评论。在我们的例子中,正如我之前提到的,底层算法是 SHA-256,其输出大小为 256 位,这正是我们设置密钥大小的值。
  • \n
  • 如果您打算使用它来存储用户密码,通常建议使用至少10,000 次迭代或更多。我已将默认值设置为 50,000,您当然可以根据需要进行更改。
  • \n
\n

代码:

\n
public static class SecretHasher\n{\n    private const int _saltSize = 16; // 128 bits\n    private const int _keySize = 32; // 256 bits\n    private const int _iterations = 50000;\n    private static readonly HashAlgorithmName _algorithm = HashAlgorithmName.SHA256;\n\n    private const char segmentDelimiter = \':\';\n\n    public static string Hash(string input)\n    {\n        byte[] salt = RandomNumberGenerator.GetBytes(_saltSize);\n        byte[] hash = Rfc2898DeriveBytes.Pbkdf2(\n            input,\n            salt,\n            _iterations,\n            _algorithm,\n            _keySize\n        );\n        return string.Join(\n            segmentDelimiter,\n            Convert.ToHexString(hash),\n            Convert.ToHexString(salt),\n            _iterations,\n            _algorithm\n        );\n    }\n\n    public static bool Verify(string input, string hashString)\n    {\n        string[] segments = hashString.Split(segmentDelimiter);\n        byte[] hash = Convert.FromHexString(segments[0]);\n        byte[] salt = Convert.FromHexString(segments[1]);\n        int iterations = int.Parse(segments[2]);\n        HashAlgorithmName algorithm = new HashAlgorithmName(segments[3]);\n        byte[] inputHash = Rfc2898DeriveBytes.Pbkdf2(\n            input,\n            salt,\n            iterations,\n            algorithm,\n            hash.Length\n        );\n        return CryptographicOperations.FixedTimeEquals(inputHash, hash);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

用法:

\n
// Hash:\nstring password = "...";\nstring hashed = SecretHasher.Hash(password);\n\n// Verify:\nstring enteredPassword = "...";\nbool isPasswordCorrect = SecretHasher.Verify(enteredPassword, hashed);\n
Run Code Online (Sandbox Code Playgroud)\n

  • 这应该是 2022 年公认的答案。漂亮。 (4认同)

Mar*_*tin 14

我使用哈希和盐进行密码加密(它与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);
}
Run Code Online (Sandbox Code Playgroud)

  • -1表示使用普通SHA-1,速度很快.使用慢键派生函数,例如PBKDF2,bcrypt或scrypt. (13认同)

ibr*_*gon 14

@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;
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
Run Code Online (Sandbox Code Playgroud)


Pan*_*ang 12

在 ASP.NET Core 中,使用PasswordHasher<TUser>.
 •命名空间:Microsoft.AspNetCore.Identity
 •大会:Microsoft.Extensions.Identity.Core.dll的NuGet |来源


要散列密码,请使用HashPassword()

var hashedPassword = new PasswordHasher<object?>().HashPassword(null, password);
Run Code Online (Sandbox Code Playgroud)

要验证密码,请使用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();
}

Run Code Online (Sandbox Code Playgroud)

优点:

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

缺点: