问题描述
我正在构建一个 Web 应用程序,需要使用用户密码对用户进行身份验证。我正在尝试将其构建为在 2021 年被认为是一种良好的安全实践。就我已经能够从我在线阅读的内容中收集到的信息而言,从客户端将密码发送到服务器通过 HTTPS(仅限)。
[编辑:关于服务器的上下文] 在服务器端,我打算为每个用户存储一个盐和他们密码的散列版本。在网络上,我显然不应该发送明文密码,但为了防止播放,我也不应该发送散列密码值。因此,下面的客户端算法。 [结束编辑]
- 用户的密码在客户端散列[编辑:使用与服务器端相同的盐]。
- Nonce 在客户端生成 [编辑:这应该是服务器生成并提供给客户端,请参阅评论]
- 散列密码加随机数在客户端进行散列。
- 随机数和最终哈希值通过 HTTPS 从客户端发送到服务器。
- 一定要清理客户端上的密码(不是在我的代码示例中)。
这是我的实验示例代码:
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:
- 获取您发送的任何数据(即密码)
- 添加盐
- 应用 PRF(伪随机函数)次数
- 返回 n 位派生密码
那么,回答您的问题:
- PBKDF2 可以运行两次,但我会增加迭代次数,而不是运行两次
- 100,000 是合理的迭代次数
- 24 字节(192 位)是合理的哈希大小。尽管您使用 HMACSHA512 作为 PFR,它会生成大小为 512 位的散列。
- PBKDF2 标准允许 8 字节 SALT,但 NIST 建议最小。 16 字节 - 我会增加 SALT 大小
- 如前所述,您可以对任何字符串输入运行 PBKDF2。在大多数情况下,它是密码或密码