使用相同用户名对不同的salt进行密码哈希



我们在网站中引入了密码加密。盐的计算如下:

Rfc2898DeriveBytes hasher = new Rfc2898DeriveBytes(Username.ToLowerInvariant(),
System.Text.Encoding.Default.GetBytes("Wn.,G38uI{~6y8G-FA4);UD~7u75%6"), 10000);
string salt = Convert.ToBase64String(hasher.GetBytes(25));

对于大多数用户名来说,盐总是一样的。但对于某些用户名,它在每次调用时都会发生变化。有人能告诉我我们做错了什么吗?

假设您也在使用RFC2898DeriveBytes对密码本身进行散列,那么@CodesInChaos是正确的,那么您做错的是:

  • 基于用户名构建salt,而不是使用加密PRNG为每个用户生成新的salt。
    • 你应该使用类似的东西。NET RNGCryptoServiceProvider类生成8到16(二进制)字节的随机salt
      • 例如,来自Rfc2898DeriveBytes示例1

byte[] salt1 = new byte[8];
using (RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider())
{
// Fill the array with a random value.
rngCsp.GetBytes(salt1);
}

  • salt应该与密码哈希和迭代次数一起存储在数据库的clear中(这样你就可以更改它),可能还有一个版本代码(这样你可以再次更改它,即你当前计算的salt方法是版本1,随机salt是版本2)。

    • 在salt上花费20000次PBKDF2迭代,而不是花费在实际的密码哈希上
  • 由于RFC2898DeriveBytes是PBKDF2-HMAC-SHA-1,并且SHA-1具有本地20字节输出,因此前20字节的10000次迭代
  • 在接下来的20个字节中再进行10000次迭代,然后将其截断为仅需要5次才能得到25个字节的输出
  • 这是一个弱点,因为防御者每次登录都必须花时间在salt上,无论是花在salt还是密码哈希上。攻击者必须为每个用户名花费一次时间,然后他们将存储结果并尝试_illions(其中_非常大)的密码猜测
    • 因此,攻击者具有比正常情况更大的边际优势,因为他们可以预先计算salt,而您必须实时计算

如果您没有使用RFC2898DeriveBytes、另一个PBKDF2实现、BCrypt或SCrypt来进行实际的密码哈希,那么这就是您做错的地方。

在某些情况下(但不是所有情况下)修剪用户名完全是偶然的;只要确保在对密码进行哈希处理之前不要对其进行修剪即可。

最新更新