使用 MD5 或 sha-256 C# 对密码进行哈希处理

发布于 2024-10-05 03:43:17 字数 168 浏览 6 评论 0原文

我正在为应用程序编写注册表单,但对于 C# 新手来说仍然存在问题。

我希望将密码加密/哈希为 md5 或 sha-256,最好是 sha-256。

有什么好的例子吗?我希望它能够从“字符串密码;”中获取信息然后对其进行散列并存储在变量“string hPassword;”中。有什么想法吗?

I'm writing a register form for a application but still having problems with being new to c#.

I am looking to encrypt/hash passwords to md5 or sha-256, preferably sha-256.

Any good examples? I want it to be able to take the information from "string password;" and then hash it and store in the variable "string hPassword;". Any ideas?

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

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

发布评论

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

评论(9

锦爱 2024-10-12 03:43:17

不要使用简单的哈希,甚至是加盐的哈希。使用某种密钥强化技术,例如 bcrypt (带有 .NET 实现)或 PBKDF2(带有 内置实现)。

这是使用 PBKDF2 的示例。

从您的密码生成密钥...

string password = GetPasswordFromUserInput();

// specify that we want to randomly generate a 20-byte salt
using (var deriveBytes = new Rfc2898DeriveBytes(password, 20))
{
    byte[] salt = deriveBytes.Salt;
    byte[] key = deriveBytes.GetBytes(20);  // derive a 20-byte key

    // save salt and key to database
}

然后测试密码是否有效...

string password = GetPasswordFromUserInput();

byte[] salt, key;
// load salt and key from database

using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
{
    byte[] newKey = deriveBytes.GetBytes(20);  // derive a 20-byte key

    if (!newKey.SequenceEqual(key))
        throw new InvalidOperationException("Password is invalid!");
}

Don't use a simple hash, or even a salted hash. Use some sort of key-strengthening technique like bcrypt (with a .NET implementation here) or PBKDF2 (with a built-in implementation).

Here's an example using PBKDF2.

To generate a key from your password...

string password = GetPasswordFromUserInput();

// specify that we want to randomly generate a 20-byte salt
using (var deriveBytes = new Rfc2898DeriveBytes(password, 20))
{
    byte[] salt = deriveBytes.Salt;
    byte[] key = deriveBytes.GetBytes(20);  // derive a 20-byte key

    // save salt and key to database
}

And then to test if a password is valid...

string password = GetPasswordFromUserInput();

byte[] salt, key;
// load salt and key from database

using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
{
    byte[] newKey = deriveBytes.GetBytes(20);  // derive a 20-byte key

    if (!newKey.SequenceEqual(key))
        throw new InvalidOperationException("Password is invalid!");
}
苍风燃霜 2024-10-12 03:43:17

您将需要使用 System.Security .Cryptography 命名空间;具体来说,MD5 类< /a> 或 SHA256

此页面上的代码中提取一些内容,以及知道两个类具有相同的基类(HashAlgorithm),您可以使用这样的函数:

public string ComputeHash(string input, HashAlgorithm algorithm)
{
   Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

   Byte[] hashedBytes = algorithm.ComputeHash(inputBytes);

   return BitConverter.ToString(hashedBytes);
}

然后您可以这样调用它(对于 MD5):

string hPassword = ComputeHash(password, new MD5CryptoServiceProvider());

或者对于 SHA256:

string hPassword = ComputeHash(password, new SHA256CryptoServiceProvider());

编辑:添加盐支持
正如 dtb 在评论中指出的那样,如果这段代码能够添​​加 salt< /a>.如果您不熟悉它,盐是一组随机位,它们作为散列函数的输入包含在内,这对于阻止针对散列密码的字典攻击有很大帮助(例如,使用 彩虹表)。这是支持 salt 的 ComputeHash 函数的修改版本:

public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
{
   Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

   // Combine salt and input bytes
   Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
   salt.CopyTo(saltedInput, 0);
   inputBytes.CopyTo(saltedInput, salt.Length);

   Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);

   return BitConverter.ToString(hashedBytes);
}

希望这对您有所帮助!

You're going to want to use the System.Security.Cryptography namespace; specifically, the MD5 class or the SHA256 class.

Drawing a bit from the code on this page, and with the knowledge that both classes have the same base class (HashAlgorithm), you could use a function like this:

public string ComputeHash(string input, HashAlgorithm algorithm)
{
   Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

   Byte[] hashedBytes = algorithm.ComputeHash(inputBytes);

   return BitConverter.ToString(hashedBytes);
}

Then you could call it like this (for MD5):

string hPassword = ComputeHash(password, new MD5CryptoServiceProvider());

Or for SHA256:

string hPassword = ComputeHash(password, new SHA256CryptoServiceProvider());

Edit: Adding Salt Support
As dtb pointed out in the comments, this code would be stronger if it included the ability to add salt. If you're not familiar with it, salt is a set of random bits that are included as an input to the hashing function, which goes a long way to thwart dictionary attacks against a hashed password (e.g., using a rainbow table). Here's a modified version of the ComputeHash function that supports salt:

public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
{
   Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

   // Combine salt and input bytes
   Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
   salt.CopyTo(saltedInput, 0);
   inputBytes.CopyTo(saltedInput, salt.Length);

   Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);

   return BitConverter.ToString(hashedBytes);
}

Hope this has been helpful!

烟若柳尘 2024-10-12 03:43:17

将密码存储在数据库中时,应始终在散列之前对其进行加盐。

推荐的数据库列:

  • PasswordSalt : int
  • PasswordHash : binary(20)

您在网上找到的大多数帖子都会讨论 ASCII 编码盐和散列,但这是不需要的,只会增加不需要的计算。另外,如果您使用SHA-1,那么输出将只有 20 个字节,因此数据库中的哈希字段长度只需 20 个字节。我理解您询问 SHA-256,但除非您有令人信服的理由,否则在大多数业务实践中使用具有盐值的 SHA-1 就足够了。如果您坚持使用 SHA-256,那么数据库中的哈希字段长度需要为 32 字节。

下面是一些函数,它们将生成盐、计算哈希值并根据密码验证哈希值。

下面的 salt 函数根据 4 个以加密方式创建的随机字节生成一个整数形式的加密强盐。

private int GenerateSaltForPassword()
{
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] saltBytes = new byte[4];
    rng.GetNonZeroBytes(saltBytes);
    return (((int)saltBytes[0]) << 24) + (((int)saltBytes[1]) << 16) + (((int)saltBytes[2]) << 8) + ((int)saltBytes[3]);
}

然后可以使用盐和以下函数对密码进行哈希处理。将盐与密码连接起来,然后计算哈希值。


private byte[] ComputePasswordHash(string password, int salt)
{
    byte[] saltBytes = new byte[4];
    saltBytes[0] = (byte)(salt >> 24);
    saltBytes[1] = (byte)(salt >> 16);
    saltBytes[2] = (byte)(salt >> 8);
    saltBytes[3] = (byte)(salt);

    byte[] passwordBytes = UTF8Encoding.UTF8.GetBytes(password);

    byte[] preHashed = new byte[saltBytes.Length + passwordBytes.Length];
    System.Buffer.BlockCopy(passwordBytes, 0, preHashed, 0, passwordBytes.Length);
    System.Buffer.BlockCopy(saltBytes, 0, preHashed, passwordBytes.Length, saltBytes.Length);

    SHA1 sha1 = SHA1.Create();
    return sha1.ComputeHash(preHashed);
}

检查密码只需计算哈希值,然后将其与预期哈希值进行比较即可完成。


private bool IsPasswordValid(string passwordToValidate, int salt, byte[] correctPasswordHash)
{
    byte[] hashedPassword = ComputePasswordHash(passwordToValidate, salt);

    return hashedPassword.SequenceEqual(correctPasswordHash);
}

You should always salt the password before hashing when storing them in the database.

Recommended database columns:

  • PasswordSalt : int
  • PasswordHash : binary(20)

Most posts you find online will talk about ASCII encoding the salt and hash, but that is not needed and only add unneeded computation. Also if you use SHA-1, then the output will only be 20 bytes so your hash field in the database only needs to be 20 bytes in length. I understand your asking about SHA-256, but unless you have a compelling reason, using SHA-1 with a salt value will be sufficient in most business practices. If you insist on SHA-256, then the hash field in the database needs to be 32 bytes in length.

Below are a few functions that will generate the salt, compute the hash and verify the hash against a password.

The salt function below generates a cryptographically strong salt as an Integer from 4 cryptographically created random bytes.

private int GenerateSaltForPassword()
{
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] saltBytes = new byte[4];
    rng.GetNonZeroBytes(saltBytes);
    return (((int)saltBytes[0]) << 24) + (((int)saltBytes[1]) << 16) + (((int)saltBytes[2]) << 8) + ((int)saltBytes[3]);
}

The password can then be hashed using the salt with the function below. The salt is concatenated to the password and then the hash is computed.


private byte[] ComputePasswordHash(string password, int salt)
{
    byte[] saltBytes = new byte[4];
    saltBytes[0] = (byte)(salt >> 24);
    saltBytes[1] = (byte)(salt >> 16);
    saltBytes[2] = (byte)(salt >> 8);
    saltBytes[3] = (byte)(salt);

    byte[] passwordBytes = UTF8Encoding.UTF8.GetBytes(password);

    byte[] preHashed = new byte[saltBytes.Length + passwordBytes.Length];
    System.Buffer.BlockCopy(passwordBytes, 0, preHashed, 0, passwordBytes.Length);
    System.Buffer.BlockCopy(saltBytes, 0, preHashed, passwordBytes.Length, saltBytes.Length);

    SHA1 sha1 = SHA1.Create();
    return sha1.ComputeHash(preHashed);
}

Checking the password can be done simply by computing the hash and then comparing it to the expected hash.


private bool IsPasswordValid(string passwordToValidate, int salt, byte[] correctPasswordHash)
{
    byte[] hashedPassword = ComputePasswordHash(passwordToValidate, salt);

    return hashedPassword.SequenceEqual(correctPasswordHash);
}

又怨 2024-10-12 03:43:17

TL;DR 使用 Microsoft.AspNetCore .Cryptography.KeyDerivation,使用 SHA-512 实现 PBKDF2。

开始使用密码哈希的好主意是查看 OWASP 指南的内容。推荐的算法列表包括 Argon2、PBKDF2、scrypt 和 bcrypt。所有这些算法都可以调整,以调整哈希密码所需的时间,以及相应地通过暴力破解密码的时间。所有这些算法都利用盐来防止彩虹表攻击。

这两种算法都不是很弱,但有一些区别:

  • bcrypt 已经存在了近 20 年,已被广泛使用并且
    经受住了时间的考验。它对 GPU 有很强的抵抗力
  • Argon2 是最新成员,是 2015 年密码哈希竞赛的获胜者 它对 GPU 和 FPGA 攻击有更好的保护,但对我来说有点太新了,
  • 我对 scrypt 了解不多。它的设计目的是阻止 GPU 和 FPGA 加速攻击,但我听说它并不像最初声称的那么强大。
  • PBKDF2 是一系列由不同哈希参数化的算法
    功能。它不提供针对 GPU 或 ASIC 攻击的特定保护,特别是在使用 SHA-1 等较弱的哈希函数时,但如果它对您很重要,则它经过 FIPS 认证,并且如果迭代次数为足够大。

仅基于算法,​​我可能会选择 bcrypt,PBKDF2 是最不受欢迎的。

然而,这并不是故事的全部,因为即使是最好的算法也可能因为糟糕的实现而变得不安全。让我们看看 .NET 平台可用的内容:

  • Bcrypt 可通过 bcrypt.net 获得。他们说该实现是基于 Java jBCrypt。目前 github 上有 6 个贡献者和 8 个问题(全部已关闭)。总的来说,看起来不错,不过,不知道是否有人对代码进行了审计,也很难判断如果发现漏洞是否会很快推出更新版本。我听说 Stack Overflow 由于这些原因不再使用 bcrypt
  • 可能使用 Argon2 的最佳方法是通过绑定到
    著名的 libsodium 库,例如
    https://github.com/adamcaudill/libsodium-net。这个想法是
    大部分加密货币是通过 libsodium 实现的,它具有相当大的
    支持,并且“未经测试”的部分非常有限。然而,在
    密码学细节意义重大,因此与 Argon2 结合起来
    相对较新,我将其视为实验性选项
  • 很长一段时间以来,.NET 通过以下方式内置了 PBKDF2 的实现
    Rfc2898DeriveBytes 类。然而,实现上只能使用SHA-1哈希函数,现在看来速度太快,不安全
  • 最后,最新的解决方案是
    Microsoft.AspNetCore.Cryptography.KeyDerivation
    可通过 NuGet 获取。它提供带有 SHA-1、SHA-256 或 SHA-512 哈希函数的 PBKDF2 算法,这比 Rfc2898DeriveBytes 好得多。这里最大的优势是该实现是由 Microsoft 提供的,虽然我无法正确评估 Microsoft 开发人员与 BCrypt.net 或 libsodium 开发人员的加密勤奋,但信任它是有意义的,因为如果您正在运行 .NET 应用程序,已经严重依赖微软了。如果发现安全问题,我们也可能期望微软发布更新。希望如此。

总结到目前为止的研究,虽然 PBKDF2 可能是四种算法中最不受欢迎的算法,但 Microsoft 提供的实现的可用性胜过这一点,因此合理的决定是使用 Microsoft.AspNetCore.Cryptography.KeyDerivation< /代码>。

目前最新的包面向 .NET Standard 2.0,因此可在 .NET Core 2.0 或 .NET Framework 4.6.1 或更高版本中使用。如果您使用早期的框架版本,则可以使用以前版本的包, 1.1.3,面向 .NET Framework 4.5.1 或 .NET Core 1.0。不幸的是,甚至无法在 .NET 的早期版本中使用它。

文档和工作示例位于 learn.microsoft.com。但是,不要按原样复制粘贴,开发人员仍然需要做出决定。

第一个决定是使用什么哈希函数。可用选项包括 SHA-1、SHA-256 和 SHA-512。其中,SHA-1 绝对太快而无法保证安全,SHA-256 还不错,但我会推荐 SHA-512,因为据说它的 64 位操作使用使得更难从基于 GPU 的攻击中受益。

然后,您需要选择密码哈希输出长度和盐长度。输出长于哈希函数输出(例如 SHA-512 的 512 位)是没有意义的,并且完全像这样可能是最安全的。对于盐的长度,意见不一。 128 位应该足够了,但无论如何,长度超过哈希输出长度肯定不会带来任何好处。

接下来是迭代计数。它越大,密码哈希就越难破解,但用户登录所需的时间就越长。我建议选择它,这样哈希在典型的生产系统上需要 0.25 - 1 秒,并且在任何情况下,它不应小于 10000。

通常,您会得到字节数组作为盐和哈希值。使用 Base64 将它们转换为字符串。您可以选择在数据库中使用两个不同的列,或者使用 Base64 中未遇到的分隔符将盐和密码合并在一列中。

不要忘记设计密码散列存储,以便将来无缝地转向更好的散列算法。

TL;DR use Microsoft.AspNetCore.Cryptography.KeyDerivation, implementing PBKDF2 with SHA-512.

The good idea to get started with password hashing is to look at what OWASP guidelines say. The list of recommended algorithms includes Argon2, PBKDF2, scrypt, and bcrypt. All these algorithms can be tuned to adjust the time it takes to hash a password, and, correspondingly, the time to crack it via brute-force. All these algorithms utilize salt to protect from rainbow tables attacks.

Neither of these algorithms is terribly weak, but there are some differences:

  • bcrypt has been around for almost 20 years, has been widely used and
    has withstood the test of time. It is pretty resistant to GPU
    attacks, but not to FPGA
  • Argon2 is the newest addition, being a winner of 2015 Password hashing competition. It has better protection against GPU and FPGA attacks, but is a bit too recent to my liking
  • I don't know much about scrypt. It has been designed to thwart GPU- and FPGA- accelerated attacks, but I've heard it turned out to be not as strong as originally claimed
  • PBKDF2 is a family of algorithms parametrized by the different hash
    functions. It does not offer a specific protection against GPU or ASIC attacks, especially if a weaker hash function like SHA-1 is used, but it is, however, FIPS-certified if it matters to you, and still acceptable if the number of iterations is large enough.

Based on algorithms alone, I would probably go with bcrypt, PBKDF2 being the least favorable.

However, it's not the full story, because even the best algorithm can be made insecure by a bad implementation. Let's look at what is available for .NET platform:

  • Bcrypt is available via bcrypt.net. They say the implementation is based on Java jBCrypt. Currently there are 6 contributors and 8 issues (all closed) on github. Overall, it looks good, however, I don't know if anyone has made an audit of the code, and it's hard to tell whether an updated version will be available soon enough if a vulnerability is found. I've heard Stack Overflow moved away from using bcrypt because of such reasons
  • Probably the best way to use Argon2 is through bindings to the
    well-known libsodium library, e.g.
    https://github.com/adamcaudill/libsodium-net. The idea is that
    the most of the crypto is implemented via libsodium, which has considerable
    support, and the 'untested' parts are pretty limited. However, in
    cryptography details mean a lot, so combined with Argon2 being
    relatively recent, I'd treat it as an experimental option
  • For a long time, .NET had a built-in an implementation of PBKDF2 via
    Rfc2898DeriveBytes class. However, the implementation can only use SHA-1 hash function, which is deemed too fast to be secure nowadays
  • Finally, the most recent solution is
    Microsoft.AspNetCore.Cryptography.KeyDerivation package
    available via NuGet. It provides PBKDF2 algorithm with SHA-1, SHA-256, or SHA-512 hash functions, which is considerably better than Rfc2898DeriveBytes. The biggest advantage here is that the implementation is supplied by Microsoft, and while I cannot properly assess cryptographic diligence of Microsoft developers versus BCrypt.net or libsodium developers, it just makes sense to trust it because if you are running a .NET application, you are heavily relying on Microsoft already. We might also expect Microsoft to release updates if security issues are found. Hopefully.

To summarize the research up to this point, while PBKDF2 might be the least preferred algorithm of the four, the availability of Microsoft-supplied implementation trumps that, so the reasonable decision would be to use Microsoft.AspNetCore.Cryptography.KeyDerivation.

The recent package at the moment targets .NET Standard 2.0, so available in .NET Core 2.0 or .NET Framework 4.6.1 or later. If you use earlier framework version, it is possible to use the previous version of the package, 1.1.3, which targets .NET Framework 4.5.1 or .NET Core 1.0. Unfortunately, it is not possible to use it in even earlier versions of .NET.

The documentation and the working example is available at learn.microsoft.com. However, do not copy-paste it as it is, there are still decisions a developer needs to make.

The first decision is what hash function to use. Available options include SHA-1, SHA-256, and SHA-512. Of those, SHA-1 is definitely too fast to be secure, SHA-256 is decent, but I would recommend SHA-512, because supposedly, its 64-bit operations usage makes it harder to benefit from GPU-based attacks.

Then, you need to choose the password hash output length and the salt length. It doesn't make sense to have output longer than the hash function output (e.g. 512 bits for SHA-512), and it would probably be the most secure to have it exactly like that. For the salt length, opinions differ. 128 bits should be enough, but in any case, the length longer than the hash output length surely doesn't provide any benefits.

Next, there is an iteration count. The bigger it is, the harder password hashes are to crack, but the longer it takes to log users in. I'd suggest to choose it so the hashing takes 0.25 - 1 seconds on the typical production system, and in any case, it should not be less than 10000.

Normally, you would get bytes array as salt and hash values. Use Base64 to convert them to strings. You can opt to use two different columns in the database, or combine salt and password in one column using a separator which is not encountered in Base64.

Don't forget to devise a password hashing storage in a way that allows to seamlessly move to a better hashing algorithm in future.

孤城病女 2024-10-12 03:43:17

如果您要存储哈希密码,请使用 bcrypt 而不是 SHA-256。问题在于 SHA-256 针对速度进行了优化,如果有人访问您的数据库,则更容易对密码进行暴力攻击。

阅读这篇文章:彩虹表已经足够了:关于安全密码方案您需要了解的内容和这个回答之前的问题。

文章中的一些引用:

问题是 MD5 速度很快。它的现代竞争对手也是如此,例如 SHA1 和 SHA256。速度是现代安全哈希的设计目标,因为哈希是几乎每个密码系统的构建块,并且通常按每个数据包或每个消息按需执行。

速度正是您在密码哈希函数中不想要的。


最后,我们了解到,如果我们想要安全地存储密码,我们有三个合理的选择:PHK 的 MD5 方案、Provos-Maziere 的 Bcrypt 方案和 SRP。我们得知正确的选择是 Bcrypt。

If you are going to be storing the hashed passwords, use bcrypt instead of SHA-256. The problem is that SHA-256 is optimized for speed, which makes it easier for a brute force attack on passwords should someone get access to your database.

Read this article: Enough With The Rainbow Tables: What You Need To Know About Secure Password Schemes and this answer to a previous SO question.

Some quotes from the article:

The problem is that MD5 is fast. So are its modern competitors, like SHA1 and SHA256. Speed is a design goal of a modern secure hash, because hashes are a building block of almost every cryptosystem, and usually get demand-executed on a per-packet or per-message basis.

Speed is exactly what you don’t want in a password hash function.


Finally, we learned that if we want to store passwords securely we have three reasonable options: PHK’s MD5 scheme, Provos-Maziere’s Bcrypt scheme, and SRP. We learned that the correct choice is Bcrypt.

绮筵 2024-10-12 03:43:17

PBKDF2 使用 HMACSHA1.......如果您想要更现代的 HMACSHA256 或 HMACSHA512 实现,并且仍然希望密钥拉伸以使算法变慢,我建议使用此 API:https://sourceforge.net/projects/pwdtknet/

PBKDF2 is using HMACSHA1.......if you want more modern HMACSHA256 or HMACSHA512 implementation and still want key stretching to make the algorithm slower I suggest this API: https://sourceforge.net/projects/pwdtknet/

岁月流歌 2024-10-12 03:43:17

这是持久性不知道的 SecuredPassword 类的完整实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;


    public class SecuredPassword
    {
        private const int saltSize = 256;
        private readonly byte[] hash;
        private readonly byte[] salt;

        public byte[] Hash
        {
        get { return hash; }
    }

    public byte[] Salt
    {
        get { return salt; }
    }

    public SecuredPassword(string plainPassword)
    {
        if (string.IsNullOrWhiteSpace(plainPassword))
            return; 

        using (var deriveBytes = new Rfc2898DeriveBytes(plainPassword, saltSize))
        {
            salt = deriveBytes.Salt;
            hash = deriveBytes.GetBytes(saltSize);
        }
    }

    public SecuredPassword(byte[] hash, byte[] salt)
    {
        this.hash = hash;
        this.salt = salt;
    }

    public bool Verify(string password)
    {
        if (string.IsNullOrWhiteSpace(password))
            return false; 

        using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
        {
            byte[] newKey = deriveBytes.GetBytes(saltSize);

            return newKey.SequenceEqual(hash);
        }
    }
}

和测试:

 public class SecuredPasswordTests
{
    [Test]
    public void IsHashed_AsExpected()
    {
        var securedPassword = new SecuredPassword("password");

        Assert.That(securedPassword.Hash, Is.Not.EqualTo("password"));
        Assert.That(securedPassword.Hash.Length, Is.EqualTo(256));
    }

    [Test]
    public void Generates_Unique_Salt()
    {
        var securedPassword = new SecuredPassword("password");
        var securedPassword2 = new SecuredPassword("password");

        Assert.That(securedPassword.Salt, Is.Not.Null);
        Assert.That(securedPassword2.Salt, Is.Not.Null);
        Assert.That(securedPassword.Salt, Is.Not.EqualTo(securedPassword2.Salt));
    }

    [Test]
    public void Generates_Unique_Hash()
    {
        var securedPassword = new SecuredPassword("password");
        var securedPassword2 = new SecuredPassword("password");

        Assert.That(securedPassword.Hash, Is.Not.Null);
        Assert.That(securedPassword2.Hash, Is.Not.Null);
        Assert.That(securedPassword.Hash, Is.Not.EqualTo(securedPassword2.Hash));
    }

    [Test]
    public void Verify_WhenMatching_ReturnsTrue()
    {
        var securedPassword = new SecuredPassword("password");
        var result = securedPassword.Verify("password");
        Assert.That(result, Is.True);
    }

    [Test]
    public void Verify_WhenDifferent_ReturnsFalse()
    {
        var securedPassword = new SecuredPassword("password");
        var result = securedPassword.Verify("Password");
        Assert.That(result, Is.False);
    }

    [Test]
    public void Verify_WhenRehydrated_AndMatching_ReturnsTrue()
    {
        var securedPassword = new SecuredPassword("password123");

        var rehydrated = new SecuredPassword(securedPassword.Hash, securedPassword.Salt);

        var result = rehydrated.Verify("password123");
        Assert.That(result, Is.True);
    }

    [Test]
    public void Constructor_Handles_Null_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword(null));
    }

    [Test]
    public void Constructor_Handles_Empty_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword(string.Empty));
    }

    [Test]
    public void Verify_Handles_Null_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(null));
    }

    [Test]
    public void Verify_Handles_Empty_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(string.Empty));
    }

    [Test]
    public void Verify_When_Null_Password_ReturnsFalse()
    {
        Assert.That(new SecuredPassword("password").Verify(null), Is.False);
    }
}

Here is a full implementation of a persistence unaware SecuredPassword class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;


    public class SecuredPassword
    {
        private const int saltSize = 256;
        private readonly byte[] hash;
        private readonly byte[] salt;

        public byte[] Hash
        {
        get { return hash; }
    }

    public byte[] Salt
    {
        get { return salt; }
    }

    public SecuredPassword(string plainPassword)
    {
        if (string.IsNullOrWhiteSpace(plainPassword))
            return; 

        using (var deriveBytes = new Rfc2898DeriveBytes(plainPassword, saltSize))
        {
            salt = deriveBytes.Salt;
            hash = deriveBytes.GetBytes(saltSize);
        }
    }

    public SecuredPassword(byte[] hash, byte[] salt)
    {
        this.hash = hash;
        this.salt = salt;
    }

    public bool Verify(string password)
    {
        if (string.IsNullOrWhiteSpace(password))
            return false; 

        using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
        {
            byte[] newKey = deriveBytes.GetBytes(saltSize);

            return newKey.SequenceEqual(hash);
        }
    }
}

And tests:

 public class SecuredPasswordTests
{
    [Test]
    public void IsHashed_AsExpected()
    {
        var securedPassword = new SecuredPassword("password");

        Assert.That(securedPassword.Hash, Is.Not.EqualTo("password"));
        Assert.That(securedPassword.Hash.Length, Is.EqualTo(256));
    }

    [Test]
    public void Generates_Unique_Salt()
    {
        var securedPassword = new SecuredPassword("password");
        var securedPassword2 = new SecuredPassword("password");

        Assert.That(securedPassword.Salt, Is.Not.Null);
        Assert.That(securedPassword2.Salt, Is.Not.Null);
        Assert.That(securedPassword.Salt, Is.Not.EqualTo(securedPassword2.Salt));
    }

    [Test]
    public void Generates_Unique_Hash()
    {
        var securedPassword = new SecuredPassword("password");
        var securedPassword2 = new SecuredPassword("password");

        Assert.That(securedPassword.Hash, Is.Not.Null);
        Assert.That(securedPassword2.Hash, Is.Not.Null);
        Assert.That(securedPassword.Hash, Is.Not.EqualTo(securedPassword2.Hash));
    }

    [Test]
    public void Verify_WhenMatching_ReturnsTrue()
    {
        var securedPassword = new SecuredPassword("password");
        var result = securedPassword.Verify("password");
        Assert.That(result, Is.True);
    }

    [Test]
    public void Verify_WhenDifferent_ReturnsFalse()
    {
        var securedPassword = new SecuredPassword("password");
        var result = securedPassword.Verify("Password");
        Assert.That(result, Is.False);
    }

    [Test]
    public void Verify_WhenRehydrated_AndMatching_ReturnsTrue()
    {
        var securedPassword = new SecuredPassword("password123");

        var rehydrated = new SecuredPassword(securedPassword.Hash, securedPassword.Salt);

        var result = rehydrated.Verify("password123");
        Assert.That(result, Is.True);
    }

    [Test]
    public void Constructor_Handles_Null_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword(null));
    }

    [Test]
    public void Constructor_Handles_Empty_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword(string.Empty));
    }

    [Test]
    public void Verify_Handles_Null_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(null));
    }

    [Test]
    public void Verify_Handles_Empty_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(string.Empty));
    }

    [Test]
    public void Verify_When_Null_Password_ReturnsFalse()
    {
        Assert.That(new SecuredPassword("password").Verify(null), Is.False);
    }
}
红玫瑰 2024-10-12 03:43:17

System.Security.Cryptography.SHA256 类应该可以解决问题:

http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256.aspx

The System.Security.Cryptography.SHA256 class should do the trick:

http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256.aspx

Spring初心 2024-10-12 03:43:17

请使用这个,因为我之前也遇到过同样的问题,但可以通过小代码片段解决它

    public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
    {
        Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

        // Combine salt and input bytes
        Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
        salt.CopyTo(saltedInput, 0);
        inputBytes.CopyTo(saltedInput, salt.Length);

        Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);


        StringBuilder hex = new StringBuilder(hashedBytes.Length * 2);
        foreach (byte b in hashedBytes)
            hex.AppendFormat("{0:X2}", b);

        return hex.ToString();

    }

Please use this as i have the same issues before but could solve it will the litle code snippet

    public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
    {
        Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

        // Combine salt and input bytes
        Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
        salt.CopyTo(saltedInput, 0);
        inputBytes.CopyTo(saltedInput, salt.Length);

        Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);


        StringBuilder hex = new StringBuilder(hashedBytes.Length * 2);
        foreach (byte b in hashedBytes)
            hex.AppendFormat("{0:X2}", b);

        return hex.ToString();

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