简体   繁体   English

密码哈希保护,防止“彩虹表”通过密码填充进行反向工程

[英]Passwords hash protection against “Rainbow tables” reverse engineering with password padding

I've come across this article describing the dangers of storing "unsalted" password hashes in the database that could be subject to reverse engineering with the use of so-called " Rainbow tables ". 我碰到了这篇文章,描述了在数据库中存储“未加盐”的密码哈希的危险,而使用所谓的“ 彩虹表 ”可能会对其进行逆向工程。

It also comes with this C# code sample that basically requires storing two hash columns in your user passwords database table (instead of a traditional - one.) The issue of such approach for me is that I already have an established database table with unsalted user password hashes, and adding a new column will require restructuring of the database. C#代码示例还附带了该示例 ,该示例基本上需要在您的用户密码数据库表中存储两个哈希列(而不是传统的-一列)。对我而言,这种方法的问题是我已经建立了一个未加盐的用户密码数据库表散列,并添加新列将需要重组数据库。 So before I do that, I was looking for a different alternative and here's what I came up with. 所以在我这样做之前,我一直在寻找其他选择,这就是我想出的。

Here's the function that instead of plainly calculating the SHA1 hash on a password pads it with a long sequence of pseudo-random (but consistent) data and then calculates the hash: 这是函数,而不是简单地在密码上计算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);
}

So my question is, can someone review this code and tell me what are the dangers of doing it this way vs. what the author suggested in his code ? 所以我的问题是,有人可以回顾一下这段代码,然后告诉我用这种方式进行编写与作者在代码中提出的建议相比有什么危险吗? In case of my code sample, I'll have to store only one hash in the database instead of two (password hash + salt hash.) 对于我的代码示例,我将只需要在数据库中存储一个哈希,而不是两个(密码哈希+盐哈希)。

A salt that's solely derived from the password is close to pointless; 仅从密码派生的盐几乎没有意义; you've just created a slightly different (but constant) hash function overall. 您刚刚创建了一个整体略有不同(但恒定)的哈希函数。 A single rainbow table (albeit a custom one) could be used to target your entire database. 单个彩虹表(尽管是自定义表)可用于定位整个数据库。

Furthermore, if a salt is derived from the password then identical passwords show up as identical "password hashes". 此外,如果从密码派生盐,则相同的密码将显示为相同的“密码哈希”。 Easy passwords will likely show up as duplicates - in effect you are generating your own rainbow table. 简单的密码很可能会显示为重复-实际上,您正在生成自己的Rainbow表。

The whole point of storing a unique , independently-generated salt for each password is so that every single password is hashed with a unique hash function. 为每个密码存储唯一的独立生成的盐的要点是,以便使用唯一的哈希函数对每个密码进行哈希处理。 Therefore there would be no single rainbow table that could be used across your entire database. 因此,将没有一个彩虹表可以在整个数据库中使用。

This will only partially mitigate the problem. 这只会部分缓解问题。 What you have done is essentially created a keyed hash function. 您所做的基本上就是创建一个键控哈希函数。

With such a function general rainbow tables will no longer be applicable, but you are still in danger if an attacker gets ahold of your entire database. 使用这种功能,一般的彩虹表将不再适用,但是如果攻击者掌握了整个数据库,您仍然处于危险之中。 In this case, he can create a new rainbow table based around this random string. 在这种情况下,他可以基于此随机字符串创建一个新的Rainbow表。 Using this new table he has a high chance of breaking into at least one account in your system. 使用此新表,他很有可能侵入您系统中的至少一个帐户。

Adding a separate salt is the equivalent of using a different hash function for each password, thus you would need a separate rainbow table for each possible salt, making the attack extremely expensive. 添加单独的salt等同于为每个密码使用不同的哈希函数,因此您将需要为每个可能的salt使用单独的Rainbow表,这使得攻击极其昂贵。 If an attacker creates a rainbow table for one salt he only can break passwords that salt. 如果攻击者为一种盐创建一个彩虹表,则他只能破解该盐的密码。

Also, I wanted to point out that it doesn't matter how much "static" randomness you add if the randomness remains constant. 另外,我想指出的是,如果随机性保持不变,则添加多少“静态”随机性并不重要。

The junk is derived from password so it doesn't have a salting effect, a rainbow table can still be generated that could be applied to your entire table. 垃圾是从密码派生的,因此不会产生咸味,仍然可以生成彩虹表,并将其应用于整个表。

But if you want to use just one column the answer is simpler: 但是,如果您只想使用一列,答案会更简单:

Just use pbkdf2 to make your hash directly, make a 64 bit (8 byte) random salt , Use a higher iteration count (4000-10000). 只需使用pbkdf2直接进行哈希处理,即可生成64位(8字节)的随机盐 ,并使用较高的迭代计数(4000-10000)。 If you column can only hold 160 bits (20 bytes) then generate 12 bytes (if your column can hold more i'd up it more to 24 bytes) and store in your column salt + hash concatenated . 如果您的列只能容纳160位(20个字节),则生成12个字节 (如果您的列可以容纳更多,则最多增加24个字节),然后将salt + hash连接起来存储在您的列中。

Thus when you are comparing you just read out the first 8 bytes to get your salt. 因此,当您进行比较时,您只需读出前8个字节即可。

You can always take the current databases hashed passwords and hash them again with a different hash algo+salt, just as if they were the original password. 您始终可以获取当前数据库的哈希密码,然后使用不同的哈希算法+盐再次对其进行哈希,就像它们是原始密码一样。 It is perfectly safe to layer hashing on top of hashing, in fact it's often more safe this way. 将散列放在散列之上是完全安全的,实际上,这样通常更安全。 Keep in mind though that you also have to be able to reverse the process or you will break things. 请记住,尽管您还必须能够逆转该过程,否则您将无法正常工作。

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) 当他们下次使用有效的用户名和密码登录时(因为只有当密码哈希值应更改或您对每个密码进行质量更改时才登录),如果没有Salt数据,则生成较大的随机Salt保存并重新生成从密码+盐中散列(最好使用比SHA1更好的东西,pbkdf2需要设置为高于10,000,但取决于您的服务器资源)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM