【发布时间】:2021-08-05 16:01:16
【问题描述】:
我正在构建一个 Web 应用程序,需要使用用户密码对用户进行身份验证。我正在尝试将其构建为 2021 年被认为是良好的安全实践。据我能够从我在网上阅读的内容中收集到的内容,从客户端发送密码到通过 HTTPS 的服务器(仅)。
[编辑:关于服务器的上下文] 在服务器端,我打算为每个用户存储一个盐和他们密码的散列版本。在网络上,我显然不应该发送明文密码,而且,为了防止播放,我也不应该发送散列密码值。因此下面的客户端算法。 [结束编辑]
- 用户的密码在客户端被散列[编辑:使用与服务器端相同的盐]。
- Nonce 在客户端生成[编辑:这应该是服务器生成并提供给客户端,请参阅评论]
- 散列密码和随机数在客户端上进行散列。
- 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, KeyDerivationPrf.HMACSHA512, ITERATIONS, HASH_SIZE);
return Convert.ToBase64String(nonce)+"$"+ Convert.ToBase64String(final);
}
我将不胜感激对上述过程的想法。我误解了它,搞砸了还是错过了什么?我也想明白:
- 可以使用两次 PBKDF2 吗?
- 对于 PBKDF 迭代来说,100,000 次迭代是否合理?
- 对于 PBKDF2,24 字节的哈希大小是否合理?
- 我认为 8 个字节对于 nonce(64 位数字)来说是一个合理的大小?
- 在 base64 和 nonce 的哈希上运行 PBKDF2 是否有问题? (它需要一个字符串输入)。
我不是安全专家,我也是 C# 菜鸟,所以请原谅任何错误。
【问题讨论】:
-
不是安全专家,所以请谨慎对待。两次使用相同的加密不会提高安全性,在某些情况下甚至会降低安全性。在您的示例中,似乎 salt 和 nonce 具有相同的目的。也就是随机生成一个数来增加熵。一个应该就够了。此外,如果您通过 HTTPS 发送它似乎就足够了。
-
你提出了一个很好的观点,我显然还没有考虑过,因为我只画了客户端代码。因此,在服务器上,我应该只存储带有恒定盐的密码的哈希值。而且我相信当我在服务器上收到随机数时,我会用随机数对存储的哈希值进行哈希处理,如果它等于我通过 https 接收到的哈希值,那么它就是匹配的。这意味着我需要将盐发送给我想的客户?!
-
将密码作为明文发送,甚至作为与服务器端存储的哈希匹配的哈希都不是 AFAIK 的好习惯。因为任何一个都可以被拦截并用于回放攻击。 HTTPS 不是防弹的,我相信有时 HTTPS 并不是真正的 P2P。
-
我开始意识到在服务器上生成随机数,将其提供给客户端,然后等待随机数计算值返回是最简单的。如果没有,您将需要服务器上的历史 nonce 列表以避免回放 - 随着时间的推移,这当然是管理不合理的数据量。
标签: c# security authentication pbkdf2 nonce