9

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

语境:

当用户注册时,我调用 CreateSaltedHash() 方法并将用户从文本字段中输入的密码传递给它。此方法在将密码存储在我的用户表的密码列中之前对密码进行加盐和哈希处理。

问题:

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

如果我再次调用 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);
4

10 回答 10

3

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

Jeff Atwood(stackoverflow 版主)推荐 Bcrypt

于 2012-11-15T13:53:43.890 回答
2

当您第一次生成散列时,您需要存储盐和最终散列 - 然后重新使用相同的盐以供将来比较。

因此,您将更改您的CreateSaltedHash方法以获取密码和盐,并编写一个新CreateSalt方法以在创建/更改密码时生成盐,该密码与最终哈希一起存储。

于 2012-11-15T13:20:51.840 回答
2

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

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 ;
}
于 2012-12-18T14:11:53.630 回答
1

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

参考这篇文章来获取一些背景信息: http ://www.h-online.com/security/features/Storing-passwords-in-uncrackable-form-1255576.html

于 2012-11-15T13:22:25.740 回答
1

正如其他答案所述;是的,您应该存储盐或从例如用户名中派生盐。

您还应该使用Rfc2898DeriveBytes它来使其更安全。

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

于 2012-11-15T13:37:47.453 回答
0

我应该将盐存放在单独的列中吗?

是的。

生成加盐哈希时是否应该使用分隔符?

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

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

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

于 2012-11-15T13:22:05.877 回答
0

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

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

于 2012-11-15T13:22:54.027 回答
0

而对于更长的答案 -

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

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

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

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

于 2012-11-15T13:23:34.533 回答
0

因为您正在生成随机盐,所以您需要将盐存储在数据库中。这样做的问题是,如果您的数据库遭到破坏,攻击者将拥有盐和散列密码,因此他们可以更容易地确定真正的密码。理想情况下,您的代码中应该有一个静态盐,这样如果您的数据库受到损害,他们仍然没有盐,如果您的代码受到损害,他们还没有数据库。

另一种解决方案可能是使用胡椒。Pepper 类似于 salt,但您不会将其与 salt 和散列密码一起存储在数据库中。它将存储在代码中。这样你就有了一个生成的随机盐和一个单独存储的常量。为了使辣椒更加随机,您可以创建一个较大字符串的子字符串,用于根据某些变量(例如用户 ID)偏移的辣椒。这又是一个内部问题,如果攻击者设法获取您的数据,他们将不会知道。

于 2012-11-15T13:31:40.217 回答
0

你应该保存消化和盐。迭代次数和摘要长度值可以是应用程序中的常量。

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

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

于 2012-11-15T14:10:18.280 回答