使用 PBKDF2 和 .NET 中的随机数进行 Web 身份验证的良好实践

问题描述

我正在构建一个 Web 应用程序,需要使用用户密码对用户进行身份验证。我正在尝试将其构建为在 2021 年被认为是一种良好的安全实践。就我已经能够从我在线阅读的内容中收集到的信息而言,从客户端将密码发送到服务器通过 HTTPS(仅限)。

[编辑:关于服务器的上下文] 在服务器端,我打算为每个用户存储一个盐和他们密码的散列版本。在网络上,我显然不应该发送明文密码,但为了防止播放,我也不应该发送散列密码值。因此,下面的客户端算法。 [结束编辑]

  1. 用户的密码在客户端散列[编辑:使用与服务器端相同的盐]。
  2. Nonce 在客户端生成 [编辑:这应该是服务器生成并提供给客户端,请参阅评论]
  3. 散列密码加随机数在客户端进行散列。
  4. 随机数和最终哈希值通过 HTTPS 从客户端发送到服务器。
  5. 一定要清理客户端上的密码(不是在我的代码示例中)。

这是我的实验示例代码

public const int HASH_SIZE = 24; // size in bytes
public const int IteraTIONS = 100000; // number of pbkdf2 iterations
public const int NONCE_SIZE = 8; // size in bytes

public static string PasswordFlow(string userPassword,byte[] userSalt)
{
    // Hash the user password + user salt
    var hpwd = KeyDerivation.Pbkdf2(userPassword,userSalt,KeyDerivationPrf.HMACSHA512,IteraTIONS,HASH_SIZE);

    // Generate an 8 byte nonce using RNGCryptoServiceProvider
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] nonce = new byte[NONCE_SIZE];
    rng.GetBytes(nonce);

    // Hash the hpwd byte[] converted to Base64 with the nonce byte array as salt
    var final = KeyDerivation.Pbkdf2(Convert.ToBase64String(hpwd),nonce,HASH_SIZE);

    return Convert.ToBase64String(nonce)+"$"+ Convert.ToBase64String(final);
}

我很感激您对上述过程的想法。我是否误解了它,搞砸了或错过了什么?我也想了解:

  • 可以使用 PBKDF2 两次吗?
  • 100,000 次迭代对于 PBKDF 迭代是否合理?
  • 24 字节是否是 PBKDF2 的合理哈希大小?
  • 我认为 8 个字节是随机数(64 位数字)的合理大小?
  • 在 base64 和 nonce 中的散列上运行 PBKDF2 是否有问题? (它需要一个字符串输入)。

我不是安全专家,我也是 C# 菜鸟,所以请原谅任何错误

解决方法

PBKDF2 旨在通过增加计算成本来减少暴力攻击。 它不是为了解决发送明文密码的问题 - 这应该通过其他安全机制 - 安全通信(即 TLS 1.3)来完成。

如果安全通信被破坏,那么您是否发送了密码的明文或散列都没有关系。

您所说的 NONCE 应该称为 SALT。

基本上,PBKFD2:

  1. 获取您发送的任何数据(即密码)
  2. 添加盐
  3. 应用 PRF(伪随机函数)次数
  4. 返回 n 位派生密码

那么,回答您的问题:

  1. PBKDF2 可以运行两次,但我会增加迭代次数,而不是运行两次
  2. 100,000 是合理的迭代次数
  3. 24 字节(192 位)是合理的哈希大小。尽管您使用 HMACSHA512 作为 PFR,它会生成大小为 512 位的散列。
  4. PBKDF2 标准允许 8 字节 SALT,但 NIST 建议最小。 16 字节 - 我会增加 SALT 大小
  5. 如前所述,您可以对任何字符串输入运行 PBKDF2。在大多数情况下,它是密码或密码