如何对密码进行哈希处理

发布于 2024-10-02 06:26:57 字数 61 浏览 0 评论 0 原文

我想在手机上存储密码的哈希值,但我不知道该怎么做。我似乎只能找到加密方法。应该如何正确地对密码进行哈希处理?

I'd like to store the hash of a password on the phone, but I'm not sure how to do it. I can only seem to find encryption methods. How should the password be hashed properly?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

情域 2024-10-09 06:26:57

考虑到今天(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);

< strong>STEP 3 将盐和密码字节组合起来供以后使用:

byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);

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

string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });

STEP 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, 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

Most of the other answers here are somewhat outdated considering today's (year 2012) best practices.

The most robust password-hashing algorithm that's natively available in .NET is PBKDF2, represented by the Rfc2898DeriveBytes class.
The following code is in a stand-alone class in this post: Another example of how to store a salted password hash. The basics are really easy, so here it is broken down:

STEP 1 Create the salt value with a cryptographic PRNG:

byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);

STEP 2 Create the Rfc2898DeriveBytes and get the hash value:

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

STEP 3 Combine the salt and password bytes for later use:

byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);

STEP 4 Turn the combined salt+hash into a string for storage

string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });

STEP 5 Verify the user-entered password against a stored password

/* 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();

Note: Depending on the performance requirements of your specific application, the value 100000 can be reduced. A minimum value should be around 10000.

有深☉意 2024-10-09 06:26:57

基于 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('

用法:

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

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

示例哈希可能是这样的

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

:您可以看到,我还在哈希中包含了迭代,以便于使用,并且如果我们需要升级,也可以升级它。


如果您对 .net core 感兴趣,我在 代码审查

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

用法:


示例哈希可能是这样的


:您可以看到,我还在哈希中包含了迭代,以便于使用,并且如果我们需要升级,也可以升级它。


如果您对 .net core 感兴趣,我在 代码审查

Based on csharptest.net's great answer, I have written a Class for this:

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('

Usage:

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

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

A sample hash could be this:

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

As you can see, I also have included the iterations in the hash for easy usage and the possibility to upgrade this, if we need to upgrade.


If you are interested in .net core, I also have a .net core version on Code Review.

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

Usage:


A sample hash could be this:


As you can see, I also have included the iterations in the hash for easy usage and the possibility to upgrade this, if we need to upgrade.


If you are interested in .net core, I also have a .net core version on Code Review.

苦妄 2024-10-09 06:26:57

更新这个答案已经严重过时了。请使用 https://stackoverflow.com/a/10402129https://stackoverflow.com/a/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);

UPDATE: THIS ANSWER IS SERIOUSLY OUTDATED. Please use the recommendations from https://stackoverflow.com/a/10402129 or https://stackoverflow.com/a/73125177 instead.

You can either use

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

or

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

To get data as byte array you could use

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

and to get back string from md5data or sha1data

var hashedPassword = ASCIIEncoding.GetString(md5data);
原野 2024-10-09 06:26:57

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

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

优点:

  • 使用新的静态 Rfc2898DeriveBytes.Pbkdf2() 方法,无需每次实例化和处置对象。

  • 使用新的< code>RandomNumberGenerator 类及其静态 GetBytes 方法(在 .NET 6 中引入)用于生成盐。接受的答案中使用的 RNGCryptoServiceProvider 类 已过时

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

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

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

    [键]:[盐]:[迭代]:[算法]

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

其他要点:

  • 我在返回的哈希字符串中使用密钥和盐的十六进制表示形式。如果您愿意,也可以使用 base64,只需将每次出现的 Convert.ToHexStringConvert.FromHexString 更改为 Convert.ToBase64Convert.FromBase64 分别。其余逻辑保持完全相同。
  • 通常推荐的盐大小为 64 位或更高。我把它设置为128位。
  • 密钥大小通常应与所选算法的自然输出大小相同 - 请参阅 此评论。在我们的例子中,正如我之前提到的,底层算法是 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);

2022 (.NET 6+) solution:

Most of the other answers here were written years ago and are therefore not taking advantage of many of the more recent features introduced in newer versions of .NET. The same thing can now be achieved much more simply and with a lot less boilerplate and noise. The solution I'm proposing also provides extra robustness by allowing you to modify the settings (e.g. iterations count, etc.) in the future without actually breaking old hashes (which is what would happen if you used the accepted answer's proposed solution, for example).

Pros:

  • Uses the new static Rfc2898DeriveBytes.Pbkdf2() method introduced in .NET 6, eliminating the need to instantiate and also dispose the object every single time.

  • Uses the new RandomNumberGenerator class and its static GetBytes method — introduced in .NET 6 — to generate the salt. The RNGCryptoServiceProvider class used in the accepted answer is obsolete.

  • Uses the CryptographicOperations.FixedTimeEquals method (introduced in .NET Core 2.1) for comparing the key bytes in the Verify method, instead of doing the comparison by hand — like the accepted answer is doing. This, in addition to removing a lot of noisy boilerplate, also nullifies timing attacks.

  • Uses SHA-256 instead of the default SHA-1 as the underlying algorithm, just to be on the safe side, as the latter is a more robust and reliable algorithm.

  • The returned string from the Hash method has the following structure:

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

    This is the most important advantage of this solution, it means we're basically including metadata about the configurations used to create the hash in the final string. This allows us to change the settings (such as the number of iterations, salt/key size, etc.) in our hasher class in the future without breaking previous hashes created with the old settings. This is something that the accepted answer, for example, doesn't take into account, as it's relying on the "current" configuration values to verify hashes.

Other points:

  • I'm using the hexadecimal representation of the key and the salt in the returned hash string. You can instead use base64 if you prefer, simply by changing every occurrence of Convert.ToHexString and Convert.FromHexString to Convert.ToBase64 and Convert.FromBase64 respectively. The rest of the logic remains exactly the same.
  • The often recommended salt size is 64 bits or above. I've set it to 128 bits.
  • The key size should normally be the same as the natural output size of your chosen algorithm — see this comment. In our case, as I mentioned earlier, the underlying algorithm is SHA-256, whose output size is 256 bits, which is precisely what we're setting our key size to.
  • If you plan to use this for storing user passwords, it's usually recommended to use at least 10,000 iterations or more. I've set the default value to 50,000, which you can of course change as you see fit.

The code:

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

Usage:

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

// Verify:
string enteredPassword = "...";
bool isPasswordCorrect = SecretHasher.Verify(enteredPassword, hashed);
撞了怀 2024-10-09 06:26:57

@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('

用法:

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

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
); 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; } } }

用法:


@csharptest.net's and Christian Gollhardt's answers are great, thank you very much. But after running this code on production with millions of record, I discovered there is a memory leak. RNGCryptoServiceProvider and Rfc2898DeriveBytes classes are derived from IDisposable but we don't dispose of them. I will write my solution as an answer if someone needs with disposed version.

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('

Usage:

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

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
); 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; } } }

Usage:



    
帅气尐潴 2024-10-09 06:26:57

在 ASP.NET Core 中,使用 PasswordHasher< ;TUser>.
<子>
 • 命名空间:Microsoft.AspNetCore.Identity
 • 程序集:Microsoft.Extensions.Identity.Core.dllNuGet |


要对密码进行哈希处理,请使用 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< /代码>)。
  • 验证密码时考虑了计时攻击 (),就像PHPGo 做到了。

缺点:

In ASP.NET Core, use PasswordHasher<TUser>.

 • Namespace: Microsoft.AspNetCore.Identity
 • Assembly: Microsoft.Extensions.Identity.Core.dll (NuGet | Source)


To hash a password, use HashPassword():

var hashedPassword = new PasswordHasher<object?>().HashPassword(null, password);

To verify a password, use 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();
}


Pros:

  • Part of the .NET platform. Much safer and trustworthier than building your own crypto algorithm.
  • Configurable iteration count and future compatibility (see PasswordHasherOptions).
  • Took Timing Attack into consideration when verifying password (source), just like what PHP and Go did.

Cons:

于我来说 2024-10-09 06:26:57

我使用哈希值和盐值进行密码加密(与 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);
}

I use a hash and a salt for my password encryption (it's the same hash that Asp.Net Membership uses):

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);
}
疯到世界奔溃 2024-10-09 06:26:57
  1. 创建盐,
  2. 用盐创建哈​​希密码
  3. 保存哈希和盐
  4. 用密码和盐解密...这样开发人员就无法解密密码
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); 
      }
 }
  1. Create a salt,
  2. Create a hash password with salt
  3. Save both hash and salt
  4. decrypt with password and salt... so developers cant decrypt password
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); 
      }
 }
花期渐远 2024-10-09 06:26:57

我认为使用 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.HMACSHA1 参数代替

KeyDerivationPrf.HMACSHA256 或 KeyDerivationPrf.HMACSHA512。

不要在密码哈希方面妥协。有许多数学上合理的方法可以优化密码哈希黑客攻击。后果可能是灾难性的。
一旦犯罪分子能够获得您用户的密码哈希表,这将是相对的
如果算法较弱或实现不正确,他很容易破解密码。
他有很多时间(时间x计算机能力)来破解密码。密码散列应该具有很强的加密强度,才能“花很多时间”
不合理的时间”。

还有一点,添加

哈希验证需要时间(而且这是好事)。
当用户输入错误的用户名时,不需要时间来检查用户名是否正确。
当用户名正确时,我们开始密码验证 - 这是一个相对较长的过程。

对于黑客来说,很容易了解用户是否存在。

当用户名错误时,请确保不要立即返回答案。

不用说:永远不要回答哪里出了问题。只是一般“凭据错误”。

I think using KeyDerivation.Pbkdf2 is better than Rfc2898DeriveBytes.

Example and explanation:
Hash passwords in 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=
 */

This is a sample code from the article. And it's a minimum security level.
To increase it I would use instead of KeyDerivationPrf.HMACSHA1 parameter

KeyDerivationPrf.HMACSHA256 or KeyDerivationPrf.HMACSHA512.

Don't compromise on password hashing. There are many mathematically sound methods to optimize password hash hacking. Consequences could be disastrous.
Once a malefactor can get his hands on password hash table of your users it would be relatively
easy for him to crack passwords given algorithm is weak or implementation is incorrect.
He has a lot of time (time x computer power) to crack passwords. Password hashing should be cryptographically strong to turn "a lot of time"
to "unreasonable amount of time".

One more point to add

Hash verification takes time (and it's good).
When user enters wrong user name it's takes no time to check that user name is incorrect.
When user name is correct we start password verification - it's relatively long process.

For a hacker it would be very easy to understand if user exists or doesn't.

Make sure not to return immediate answer when user name is wrong.

Needless to say : never give an answer what is wrong. Just general "Credentials are wrong".

魔法少女 2024-10-09 06:26:57

首先使用下面的类生成盐。每个用户都需要有不同的盐,我们可以将其与其他用户属性一起保存在数据库中。 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();

    }
}

输出

Use the below class to Generate a Salt first. Each user needs to have a different salt, we can save it in the database along with the other user properties. The rounds value decides the number of times the password will be hashed.

For more details: 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));
        }
    }
}

We can call it from a console application as follows. I have hashed the password twice using the same salt.

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(
quot;hashedPassword1 :{hashedPassword1}");
        Console.WriteLine(
quot;hashedPassword2 :{hashedPassword2}");
        Console.WriteLine(hashedPassword1.Equals(hashedPassword2));

        Console.ReadLine();

    }
}

Output

旧时浪漫 2024-10-09 06:26:57
  var sha1 = SHA1.Create();      

  byte[] bytes = Encoding.UTF8.GetBytes("Your String Data");

  byte[] hash = sha1.ComputeHash(bytes);


    var sBuilder = new StringBuilder();

    for (int i = 0; i < hash.Length; i++)
    {
        sBuilder.Append(hash[i].ToString("X"));
    }

    Console.WriteLine(sBuilder.ToString());  //Hash value in Hex
  var sha1 = SHA1.Create();      

  byte[] bytes = Encoding.UTF8.GetBytes("Your String Data");

  byte[] hash = sha1.ComputeHash(bytes);


    var sBuilder = new StringBuilder();

    for (int i = 0; i < hash.Length; i++)
    {
        sBuilder.Append(hash[i].ToString("X"));
    }

    Console.WriteLine(sBuilder.ToString());  //Hash value in Hex
青丝拂面 2024-10-09 06:26:57

这是使用 .NET 7.0 的示例

using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System.Security.Cryptography;

Console.Write("Enter a password: ");
string? password = Console.ReadLine();

// Generate a 128-bit salt using a sequence of
// cryptographically strong random bytes.
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8); // divide by 8 to convert bits to bytes
Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");

// derive a 256-bit subkey (use HMACSHA256 with 100,000 iterations)
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
    password: password!,
    salt: salt,
    prf: KeyDerivationPrf.HMACSHA256,
    iterationCount: 100000,
    numBytesRequested: 256 / 8));

Console.WriteLine($"Hashed: {hashed}");

/*
 * SAMPLE OUTPUT
 *
 * Enter a password: Xtw9NMgx
 * Salt: CGYzqeN4plZekNC88Umm1Q==
 * Hashed: Gt9Yc4AiIvmsC1QQbe2RZsCIqvoYlst2xbz0Fs8aHnw=
 */

Here's an example using .NET 7.0

using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System.Security.Cryptography;

Console.Write("Enter a password: ");
string? password = Console.ReadLine();

// Generate a 128-bit salt using a sequence of
// cryptographically strong random bytes.
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8); // divide by 8 to convert bits to bytes
Console.WriteLine(
quot;Salt: {Convert.ToBase64String(salt)}");

// derive a 256-bit subkey (use HMACSHA256 with 100,000 iterations)
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
    password: password!,
    salt: salt,
    prf: KeyDerivationPrf.HMACSHA256,
    iterationCount: 100000,
    numBytesRequested: 256 / 8));

Console.WriteLine(
quot;Hashed: {hashed}");

/*
 * SAMPLE OUTPUT
 *
 * Enter a password: Xtw9NMgx
 * Salt: CGYzqeN4plZekNC88Umm1Q==
 * Hashed: Gt9Yc4AiIvmsC1QQbe2RZsCIqvoYlst2xbz0Fs8aHnw=
 */
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文