针对“彩虹表”的密码散列保护;用密码填充的逆向工程

本文关键字:密码 保护 填充 逆向工程 彩虹表 针对 彩虹 | 更新日期: 2023-09-27 18:04:41

我看到了这篇文章,它描述了在数据库中存储"未加盐"的密码哈希值的危险,这些哈希值可能会受到使用所谓的"彩虹表"进行逆向工程的影响。

它还附带了这个c#代码示例,基本上需要在用户密码数据库表中存储两个哈希列(而不是传统的一个)。对我来说,这种方法的问题在于,我已经建立了一个包含无盐用户密码哈希的数据库表,添加新列将需要重构数据库。所以在我这么做之前,我一直在寻找一个不同的选择,这是我想到的。

下面的函数不是直接计算密码的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);
}

所以我的问题是,谁能检查一下这段代码并告诉我这样做的危险与作者在他的代码中建议的相比是什么?在我的代码示例中,我只需要在数据库中存储一个散列而不是两个(密码散列+盐散列)

针对“彩虹表”的密码散列保护;用密码填充的逆向工程

仅从密码导出的salt几乎是没有意义的;您只是创建了一个稍微不同(但不变)的散列函数。单个彩虹表(尽管是自定义的)可以用于针对整个数据库。

此外,如果从密码派生出盐,则相同的密码显示为相同的"密码哈希"。简单的密码可能会显示为重复-实际上您正在生成自己的彩虹表。

为每个密码存储一个唯一的独立生成的盐的全部意义在于,这样每个密码都可以用唯一的哈希函数进行哈希。因此,不存在可以在整个数据库中使用的单个彩虹表。

这只能部分缓解问题。您所做的实际上是创建了一个键控散列函数。

有了这样的函数,一般的彩虹表将不再适用,但是如果攻击者掌握了整个数据库,您仍然处于危险之中。在这种情况下,他可以基于这个随机字符串创建一个新的彩虹表。使用这个新表,他很有可能闯入您系统中的至少一个帐户。

添加单独的盐相当于为每个密码使用不同的哈希函数,因此您需要为每个可能的盐使用单独的彩虹表,这使得攻击非常昂贵。如果攻击者为一个盐创建了彩虹表,那么他只能破解该盐的密码。

另外,我想指出的是,如果随机性保持不变,你添加多少"静态"随机性并不重要。

垃圾是从密码派生的,所以它没有盐化效果,仍然可以生成一个彩虹表,可以应用于您的整个表。

但是如果你想只用一列,答案很简单:

只需使用pbkdf2直接生成哈希,生成64位(8字节)随机盐,使用更高的迭代计数(4000-10000)。如果你的列只能容纳160位(20字节),那么生成12个字节(如果你的列可以容纳更多,我会把它增加到24字节)并存储在你的列盐+哈希连接

因此,当你比较你只是读出前8个字节得到你的盐

您总是可以使用当前数据库散列密码并用不同的散列算法+salt再次散列它们,就像它们是原始密码一样。在哈希之上分层哈希是非常安全的,实际上这种方式通常更安全。但要记住,你也必须能够逆转这个过程,否则你会破坏东西。

对数据库进行重组,只添加一个盐字段是更好的选择(或者如果要正确地做的话,这是唯一的选择,但是您可以使用当前的哈希字段来存储盐,正如其他人发布的)

当他们下次使用有效的用户名和密码登录时(因为这只有在密码哈希值应该更改或您对每个人进行大规模强制更改密码时),如果没有Salt数据,则生成大的随机盐,保存它并从密码+盐重新生成哈希(理想情况下使用比SHA1更好的东西,pbkdf2需要设置高于10,000,但取决于您的服务器资源)