使用MD5或sha-256 C#哈希密码#

时间:2010-12-01 22:43:48

标签: c# hash sha256

我正在为一个应用程序编写一个注册表单,但仍然遇到了c#的新手问题。

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

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

9 个答案:

答案 0 :(得分:78)

不要使用简单的哈希,甚至是盐渍哈希。使用某种密钥加强技术,例如bcrypt(使用.NET implementation here)或PBKDF2(使用built-in implementation)。

以下是使用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!");
}

答案 1 :(得分:53)

您将要使用System.Security.Cryptography命名空间;具体而言,MD5 classSHA256 class

this page上的代码中略过一点,并且知道两个类具有相同的基类(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的能力,则此代码会更强。如果您不熟悉它,salt是一组随机位,作为哈希函数的输入包含在内,这对于阻止对哈希密码的字典攻击有很大帮助(例如,使用rainbow table )。这是支持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);
}

希望这有帮助!

答案 2 :(得分:5)

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

推荐的数据库列:

  • PasswordSalt:int
  • PasswordHash:binary(20)

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

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

下面的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]);
}

然后可以使用具有以下功能的salt对密码进行哈希处理。 salt连接到密码,然后计算哈希值。


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

答案 3 :(得分:2)

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

阅读本文:Enough With The Rainbow Tables: What You Need To Know About Secure Password Schemes以及此answer以前的SO问题。

文章中的一些引用:

  

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

     

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


  

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

答案 4 :(得分:2)

PBKDF2正在使用HMACSHA1 .......如果你想要更现代的HMACSHA256或HMACSHA512实现,并且仍然需要键拉伸以使算法更慢,我建议使用这个API:https://sourceforge.net/projects/pwdtknet/

答案 5 :(得分:2)

以下是持久性未发现的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);
    }
}

答案 6 :(得分:2)

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

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

这些算法都不是非常弱,但存在一些差异:

  • bcrypt已经存在了近20年,已被广泛使用和 经受住了时间的考验。它非常耐GPU 攻击,但不是FPGA
  • 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实现的,这有很大的作用 支持,以及未经测试的&#39;零件非常有限。但是,在 加密细节意味着很多,所以结合Argon2 相对较新,我将其视为实验选项
  • 很长一段时间,.NET都内置了PBKDF2的实现 Rfc2898DeriveBytes课程。但是,该实现只能使用SHA-1哈希函数,这被认为现在太快而不安全
  • 最后,最新的解决方案是 Microsoft.AspNetCore.Cryptography.KeyDerivation包 可通过NuGet获取。它为PBKDF2算法提供SHA-1,SHA-256或SHA-512哈希函数,这比Rfc2898DeriveBytes要好得多。这里的最大优点是实现是由Microsoft提供的,虽然我无法正确评估Microsoft开发人员与BCrypt.net或libsodium开发人员的加密努力,但是信任它是有道理的,因为如果您运行的是.NET应用程序,那么已经严重依赖微软。如果发现安全问题,我们可能还希望Microsoft发布更新。希望。

到目前为止总结一下这项研究,虽然PBKDF2可能是四者中最不优选的算法,但微软提供的实现的可用性胜过这一点,因此合理的决定是使用Microsoft.AspNetCore.Cryptography.KeyDerivation

目前最新的软件包目标是.NET Standard 2.0,因此可以在.NET Core 2.0或.NET Framework 4.6.1或更高版本中使用。如果使用早期的框架版本,则可以使用包含.NET Framework 4.5.1或.NET Core 1.0的软件包的先前版本1.1.3。不幸的是,即使在早期版本的.NET中也无法使用它。

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

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

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

接下来,有一个迭代计数。它越大,密码哈希越难破解,但登录用户的时间越长。我建议选择它,以便在典型的生产系统上进行哈希需要0.25-1秒,无论如何,它不应低于10000。

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

不要忘记设计一个密码哈希存储,以便将来能够无缝地转移到更好的哈希算法。

答案 7 :(得分:1)

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

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

答案 8 :(得分:1)

请使用此功能,因为我之前遇到过相同的问题,但可以解决这个问题吗?

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

    }