使用C#保护用户密码——Rfc2898DeriveBytes与SHA512的比较

4
我一直在阅读关于在数据库中保护用户密码的文章(https://crackstation.net/hashing-security.htm)。基本思想已经理解 - 生成随机盐,将其附加到密码上并对密码进行哈希处理。
这是我所做的(我没有在此处放置一些执行字符串转换的方法):
RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create();
byte[] rndBytes = new byte[512];
randomNumberGenerator.GetBytes(rndBytes);
string salt = ToHexString(rndBytes);

var sha512Hasher = SHA512.Create();
string hashedPwd = ToHexString(sha512Hasher.ComputeHash(GetBytes(pwd + salt)))

根据文章所述,这是安全的,但可以通过使用“密钥拉伸”来增加安全性。我理解的“密钥拉伸”是使用参数进行缓慢哈希处理,以使暴力破解密码更加困难。
所以这是我做的:
RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create();
byte[] salt = new byte[512];
randomNumberGenerator.GetBytes(salt);
Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(user.Password, salt, 1000);
byte[] hashBytes = k1.GetBytes(512);
string hash = ToHexString(hashBytes);

现在,这里有我的问题:

  1. SHA512Rfc2898DeriveBytes之间的区别是什么?哪一个更安全?
  2. 我应该使用更多迭代次数但较小的盐吗?这样会使它更安全吗?
  3. 在1000次迭代中,它运行得非常快——它应该有多慢?半秒钟?一秒钟?这里有什么经验法则吗?
  4. 在数据库上,我应该将字节数组转换为字符串并存储字符串,还是应该将字节数组存储在二进制数据字段中?

编辑(另外的问题)

  1. 如果我对SHA512进行1000次重新哈希,它是否会提供相同的安全性?
1个回答

6
  1. SHA512和Rfc2898DeriveBytes有什么区别?

SHA512是一种加密散列函数,而Rfc2898DeriveBytes是一种密钥派生函数。正如您所写的那样,散列函数太快了,可以轻易地被暴力破解,这就是为什么我们需要具有成本因素的函数,例如BCrypt、SCrypt、PBKDF2或Argon2。据我所知,Rfc2898DeriveBytes使用带有SHA1的HMAC实现了PBKDF2。这回答了您其他问题的答案,即迭代的SHA比Rfc2898DeriveBytes不安全。

  1. 更多迭代次数意味着盐应该更小吗?

盐和成本因素没有关系,具有不同的目的。盐防止使用彩虹表,而迭代是针对暴力攻击的一种措施。您可以从我的安全密码存储教程中获得更多信息。所以,不要使盐变短。

  1. 它应该有多慢?

当然,这取决于您的服务器和对安全性的要求,越慢意味着越难以暴力破解。一个经验法则是对于单个哈希值约需要50毫秒

  1. 在数据库中,我应该将字节数组转换为字符串吗?

这取决于您。字符串更容易处理备份、迁移和调试,而字节数组在数据库中需要更少的空间。也许您还应该看看BCrypt.Net,它生成字符串作为输出,其中包含盐,并且容易存储在单个数据库字段[string]中。


所以根据您所说的,第二个例子 - 生成盐并使用Rfc2898DeriveBytes进行迭代比第一个例子更安全? - developer82
@developer82 - 没错,对于存储密码来说这更加安全。 - martinstoeckli
我会在这里添加一条评论,关于密钥拉伸大于底层哈希函数是毫无意义的。OPs PBKDF2代码的结果长度为1KB,远远超出了应有的范围。 - Luke Joshua Park

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接