验证盐渍哈希

时间:2012-11-15 13:17:10

标签: c# hash salt password-hash

我是C#的新手,这是我的第一个问题,所以我提前为任何失礼道歉。

上下文

当用户注册时,我调用CreateSaltedHash()方法并从文本字段中传递用户输入的密码。在将密码存储在User表的Password列中之前,此方法会对密码进行加密和散列。

问题:

当用户尝试登录时,我该如何验证密码?

如果再次调用CreateSaltedHash()方法,由于随机盐,它将无法匹配。

我应该将盐储存在单独的栏中吗?我应该在生成盐渍哈希时使用分隔符吗?根据盐渍和散列密码验证输入密码的最安全方法是什么?

代码: 这是我到目前为止所做的。

public class PasswordHash
{
    public const int SALT_BYTES = 32;

    /*
     * Method to create a salted hash
     */
    public static byte[] CreateSaltedHash(string password)
    {
        RNGCryptoServiceProvider randromNumberGenerator = new RNGCryptoServiceProvider();
        byte[] salt = new byte[SALT_BYTES];
        randromNumberGenerator.GetBytes(salt);
        HashAlgorithm hashAlgorithm = new SHA256Managed();
        byte[] passwordByteArray = Encoding.UTF8.GetBytes(password);
        byte[] passwordAndSalt = new byte[passwordByteArray.Length + SALT_BYTES];
        for (int i = 0; i < passwordByteArray.Length; i++)
        {
            passwordAndSalt[i] = passwordByteArray[i];
        }
        for (int i = 0; i < salt.Length; i++)
        {
            passwordAndSalt[passwordByteArray.Length + i] = salt[i];
        }
        return hashAlgorithm.ComputeHash(passwordAndSalt);
    }

    public static bool OkPassword(string password)
    {
        //This is where I want to validate the password before logging in.
    }
}


在Register类中调用该方法。

User user= new User();
user.password = PasswordHash.CreateSaltedHash(TextBoxUserPassword.Text);

10 个答案:

答案 0 :(得分:3)

你可以使用Bcrypt.Net;它有很多非常安全的建议,而且非常容易使用。据我了解,当您创建密码时,它会自动为您生成一个唯一的盐,然后将其存储在哈希密码字符串中;因此,您不要单独存储salt,而是在与散列密码相同的字段中存储。重点是每个密码都有自己的盐,这使得黑客破解多个密码变得更加困难(耗时)。 Bcrypt使用的算法也是CPU密集型的,因此需要大量的计算能力(=金钱)才能破解。

Jeff Atwood(stackoverflow主持人)recommends Bcrypt

答案 1 :(得分:2)

当您第一次生成哈希时,您需要存储salt和最终哈希值 - 然后重新使用相同的盐进行比较。

因此,您需要更改CreateSaltedHash方法以获取密码和salt,并编写新的CreateSalt方法以在创建/更改密码时生成salt,该密码与最终密码一起存储散列。

答案 2 :(得分:2)

我建议您使用ServiceStack的SaltedHash,您可以从Nuget安装它。 只需在您的Nuget控制台中输入Install-Package ServiceStack,您就可以在代码中使用以下导入。

using ServiceStack.ServiceInterface.Auth;

然后你会产生你的盐和哈希值更容易并且绝对更快比以前更好。 只需输入以下代码:

class Security
{
   ...
   public void generate(string Password)
   {
   string hash, salt;
   new SaltedHash().GetHashAndSaltString(Password,out hash,out salt);
   //Store the hash and salt
   }
   ...
}

,您必须存储哈希和盐才能运行您的OkPassword方法。

public bool OkPassword(string Password)
{
   var hash = //getStoredHash
   var salt = //getStoredSalt
   bool verify = new SaltedHash().VerifyHashString(Password, hash , salt);
   return verify ;
}

答案 3 :(得分:1)

您必须将盐与哈希一起存储。

请参阅此文章以获取一些背景信息: http://www.h-online.com/security/features/Storing-passwords-in-uncrackable-form-1255576.html

答案 4 :(得分:1)

正如另一个回答的那样;是的,你应该存储盐或从例如用户名派生它。

您还应该使用Rfc2898DeriveBytes来提高其安全性。

这是一篇关于该主题的好文章: Password salt and hashing in C#

答案 5 :(得分:0)

  

我应该将盐储存在单独的栏中吗?

  

生成盐渍哈希时我应该使用分隔符吗?

没有必要,但只要在验证时包含相同的分隔符,它也不会受到伤害。

  

根据盐渍和散列密码验证输入密码的最安全方法是什么?

SHA512,SHA-2或-3比SHA256更安全,但你需要更高的安全性吗?

答案 6 :(得分:0)

您可以存储盐,也可以每次使用相同的盐。我建议存储盐,因为它比在所有用户中使用相同的盐更安全。

我的大多数表都有一个列,其中包含创建行的日期时间。我使用此值的DateTime结构的Ticks属性来对哈希进行加盐,但您可以使用任何您喜欢的内容,只要您每次都为用户使用相同的salt即可。需要注意的一件事是,如果您使用此方法,并且您使用的是SQL类型DateTime(而不是DateTime2),则存在精度问题。如果您在代码中创建DateTime,则需要截断它(我相信百分之一秒)。

答案 7 :(得分:0)

对于更长的答案 -

创建的每个密码都应该是随机的。这将使其本身独一无二。由于这种“随机性”,您几乎永远无法以编程方式找到与文件关联的哈希值。

加密密码的方式(没有哈希)应该是相同的,所以每次使用这种方法的反向就足够了。
PS:(更安全的验证方式)您可以将加密密码反转为原始 [Hard] ,或者使用哈希加密验证密码,并确保加密的密码与存储在DB [Preferred] 中的密码相匹配。

因此,您需要将加密的密码以及与之关联的哈希值存储在数据库中。

这将是收集验证密码所需的所有信息的方法。

答案 8 :(得分:0)

因为您要生成随机盐,所以需要将salt存储在数据库中。问题在于,如果您的数据库受到攻击,攻击者将拥有salt和哈希密码,因此他们可以更轻松地确定真实密码。理想情况下,您的代码中应该有一个静态salt,这样如果您的数据库被泄露,他们仍然没有盐,如果您的代码被泄露,他们还没有数据库。

另一个解决方案可能是使用胡椒。 Pepper类似于salt,但您不会使用salt和哈希密码将数据库存储在数据库中。它将存储在代码中。这样,您将生成一个随机盐,并将一个常量分开存储。为了使胡椒更随机,您可以创建一个更大的字符串的子字符串,用于胡椒,该胡椒根据某些变量(例如用户ID)进行偏移。这又是一个内部事件,如果他们设法获取您的数据,攻击就不会知道。

答案 9 :(得分:0)

你应该保存消化物和盐。迭代和digestLength值可以是应用程序中的常量。

byte[] getNewSalt(Int32 size)
{
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] salt = new byte[size];
    rng.GetBytes(salt);
    return salt;
}


byte[] getPasswordDigest(byte[] value, byte[] salt, Int32 iterations, Int32 digestLength)
{
    Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(value, salt, iterations);
    return deriveBytes.GetBytes(digestLength);
}

最近的文章建议,为了进一步保护密码,您可以将密码拆分为多个部分,对各个部分进行散列,然后将它们存储在数据库中的单独表中。