.NET 中使用 PBKDF2 和 nonce 的良好实践 Web 身份验证

Posted

技术标签:

【中文标题】.NET 中使用 PBKDF2 和 nonce 的良好实践 Web 身份验证【英文标题】:Good practice web authentication with PBKDF2 and nonce in .NET 【发布时间】: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 接收到的哈希值,那么它就是匹配的。这意味着我需要将盐发送给我想的客户?! 实际上是的,我什至没有考虑过您的服务器在收到一些 salt+hash 时会如何处理身份验证。您可能想查看许多协议用于身份验证的SASL。但是,您可能只想以明文形式发送密码,因为 HTTPS 的全部目的是提供安全通信。见this 将密码作为明文发送,甚至作为与服务器端存储的哈希匹配的哈希都不是 AFAIK 的好习惯。因为任何一个都可以被拦截并用于回放攻击。 HTTPS 不是防弹的,我相信有时 HTTPS 并不是真正的 P2P。 我开始意识到在服务器上生成随机数,将其提供给客户端,然后等待随机数计算值返回是最简单的。如果没有,您将需要服务器上的历史 nonce 列表以避免回放 - 随着时间的推移,这当然是管理不合理的数据量。 【参考方案1】:

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

如果安全通信被破坏,那么无论您发送密码的明文还是哈希值都无关紧要。

您所说的 NONCE 应该称为 SALT。

基本上,PBKFD2:

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

所以,回答你的问题:

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

【讨论】:

谢谢。我描述中的盐是哈希如何存储在服务器上。 nonce 由客户端生成并与 nonce 散列值一起发送到服务器。因此,在服务器端,我可以使用来自客户端的随机数对存储的哈希进行哈希处理。这实现了匹配密码的目标,而无需通过线路发送任何可用于重播的常量。 AFAIU HTTPS 可以通过例如检查。您的 orgs 防火墙,所以我知道最好不要以重播友好的方式发送密码 ;-)

以上是关于.NET 中使用 PBKDF2 和 nonce 的良好实践 Web 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

ruby 在Ruby中安全实施salted PBKDF2密码哈希(请参阅https://crackstation.net/hashing-security.htm)

在 Java 中使用 PBKDF2 进行密码验证

在 VB.NET 中实现 bittrex API

Chrome 未在我的 OpenID Connect 工作流程 (ASP.NET OWIN) 中保存/返回 Nonce cookie

Crypto++ pbkdf2 输出不同于 Rfc2898DeriveBytes (C#) 和 crypto.pbkdf2 (JavaScript)

使用 pbkdf2 的 SALT 和 HASH