我遇到了this article,描述了存储"无盐"数据库中的密码哈希值可以通过使用所谓的" Rainbow tables"进行逆向工程。
它还带有这个C# code sample,它基本上需要在你的用户密码数据库表中存储两个哈希列(而不是传统的 - 一个。)对我来说这种方法的问题是我已经建立了一个数据库使用未加盐的用户密码哈希表,添加新列将需要重新构建数据库。所以在我这样做之前,我一直在寻找另一种选择,而这就是我想出的。
这里的功能是,不是明确地计算密码上的SHA1哈希,而是用一长串伪随机(但一致)数据填充它,然后计算哈希值:
byte[] computeSecureHash(string strUserPassword)
{
//RETURN: = SHA1 byte array on the 'strUserPassword'
//Make simple junk array based on the password
ushort v = 117;
byte[] arrJunk = new byte[24];
for (int c = 0, i = 0; i < arrJunk.Length; i++)
{
v ^= strUserPassword[c++];
v *= 7;
arrJunk[i] = (byte)v;
if (c >= strUserPassword.Length)
c = 0;
}
//Make crypto byte array based on the password
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(strUserPassword, arrJunk);
pbkdf2.IterationCount = 1000;
byte[] arrCrypto = pbkdf2.GetBytes(128);
//Pad actual password
string strUserPassword_Padded = "";
const string strChars2Use = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`-=[]\\;',./~!@#$%^&*()_+{}|:\"<>?";
int nHalfArrCrypto = arrCrypto.Length / 2;
//Left side
for (int i = 0; i < nHalfArrCrypto; i++)
{
strUserPassword_Padded += strChars2Use[arrCrypto[i] % strChars2Use.Length];
}
strUserPassword_Padded += strUserPassword;
//Right side
for (int i = nHalfArrCrypto; i < arrCrypto.Length; i++)
{
strUserPassword_Padded += strChars2Use[arrCrypto[i] % strChars2Use.Length];
}
//For user's password "123"
//the 'strUserPassword_Padded' becomes:
//"bwDR]_B>H5t-k:eIq?r_wGBWqWfs#tcAE~DQ5?(Pbj#<+Cw:9(r!B[f_.S<pCjn-123b9l3<Sz^D~>G}v)?NuHT4BZ-pI2$W[kW1e4KO\"`rTg3H`}&jmtrFh1J5c72:})tQ"
//And now compuse SHA1 on the padded password
SHA1 sha1 = new SHA1CryptoServiceProvider();
byte[] bytesInputData = System.Text.Encoding.UTF8.GetBytes(strUserPassword_Padded);
return sha1.ComputeHash(bytesInputData);
}
所以我的问题是,是否有人可以审核此代码并告诉我这样做的危险性与作者在his code中建议的方式有什么危险?在我的代码示例的情况下,我只需要在数据库中存储一个哈希而不是两个(密码哈希+盐哈希。)
答案 0 :(得分:6)
完全来自密码的盐几乎没有意义;你刚刚创建了一个稍微不同(但是常量)的哈希函数。可以使用单个彩虹表(尽管是自定义彩虹表)来定位整个数据库。
此外,如果从密码派生盐,则相同的密码显示为相同的“密码哈希”。简单的密码可能会显示为重复密码 - 实际上您正在生成自己的彩虹表。
为每个密码存储唯一,独立生成的 salt的重点是,每个密码都使用唯一进行哈希处理哈希函数。因此,没有一个彩虹表可以在整个数据库中使用。
答案 1 :(得分:3)
这只会部分缓解这个问题。你所做的基本上是创建一个键控哈希函数。
通过这样的功能,一般的彩虹表将不再适用,但如果攻击者获得了整个数据库,您仍然处于危险之中。在这种情况下,他可以基于这个随机字符串创建一个新的彩虹表。使用这个新表,他很有可能进入系统中的至少一个帐户。
添加单独的salt相当于为每个密码使用不同的哈希函数,因此每个可能的盐需要一个单独的彩虹表,这使得攻击非常昂贵。如果攻击者为一个盐创建一个彩虹表,他只能破坏那些盐的密码。
另外,我想指出,如果随机性保持不变,你添加多少“静态”随机性并不重要。
答案 2 :(得分:2)
垃圾是从密码派生的,所以它没有盐析效果,仍然可以生成彩虹表,可以应用于整个表格。
但是如果你只想使用一列,答案就更简单了:
只需使用pbkdf2 直接制作哈希,制作64位(8字节)随机盐,使用更高的迭代次数(4000-10000)。如果你的列只能容纳160位(20字节),那么生成12个字节(如果你的列可以容纳更多,那么它更多就是24字节)并存储在你的列中 salt + hash concatenated 。
因此,在比较时,只需读出前8个字节即可获得盐分。
答案 3 :(得分:0)
您始终可以使用当前数据库哈希密码并使用不同的哈希算法+盐再次哈希,就像它们是原始密码一样。在散列之上进行层次散列是完全安全的,事实上它通常更安全。请记住,你还必须能够扭转这个过程,否则你就会破坏它。
答案 4 :(得分:0)
重组数据库只是添加一个盐场是更好的选择(或者唯一的一个,如果你要正确地做,但你可以使用你的醋栗哈希字段来存储盐,就像其他人发布的那样)
当他们接下来使用有效的用户名和密码登录时(因为这只是在密码哈希应该更改时或者你每次都强制更改密码时)如果没有Salt数据那么生成大的随机盐保存它并从密码+ salt重新生成哈希(理想情况下使用比SHA1更好的东西,pbkdf2需要设置为高于10,000,但取决于您的服务器资源)