0

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

它还附带这个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);
}

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

4

5 回答 5

6

仅从密码派生的盐几乎毫无意义。您刚刚创建了一个稍微不同(但不变)的散列函数。可以使用单个彩虹表(尽管是自定义的)来定位整个数据库。

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

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

于 2013-03-20T00:35:20.013 回答
3

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

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

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

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

于 2013-03-20T00:41:44.760 回答
2

垃圾来自密码,因此它没有盐渍效果,仍然可以生成可以应用于整个表的彩虹表。

但是,如果您只想使用一列,答案会更简单:

只需使用 pbkdf2直接制作您的哈希,制作一个 64 位(8 字节)随机盐,使用更高的迭代次数(4000-10000)。如果您的列只能容纳 160 位(20 字节),则生成 12 字节(如果您的列可以容纳更多,我会将其增加到 24 字节)并存储在您的列中salt + hash concatenated

因此,当您进行比较时,您只需读出前 8 个字节即可获得盐。

于 2013-03-20T13:40:17.020 回答
0

您始终可以使用当前数据库散列密码并使用不同的散列算法+盐再次散列它们,就像它们是原始密码一样。在散列之上分层散列是完全安全的,实际上这种方式通常更安全。但请记住,您还必须能够逆转该过程,否则您会破坏事情。

于 2013-03-20T00:43:37.900 回答
0

restructuring of the database to just add an salt field is better option (or the only one really if your going to do it properly, but you could use your currant hash field to store the salt, as other person posted)

when they next log in with an valid user name and password (as this is only when the password hash should change or you do mass force change password on every one) if there is no Salt data then generate large random salt save it and regenerate the hash from the password+salt (ideally using something better then SHA1, pbkdf2 needs to be set higher then 10,000 but depends on your server resources)

于 2013-03-21T05:31:30.117 回答