Защита паролем хеша от обратного инжиниринга "Радужных таблиц" с добавлением пароля

Я сталкивался с этой статьей, описывающей опасность хранения "несоленых" хэшей паролей в базе данных, которые могут быть подвергнуты обратному инжинирингу с использованием так называемых " Радужных таблиц".

Он также поставляется с этим примером кода 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);
}

Поэтому мой вопрос заключается в том, может ли кто-нибудь просмотреть этот код и сказать мне, в чем заключается опасность такого подхода по сравнению с тем, что автор предложил в своем коде? В случае моего примера кода мне придется хранить только один хэш в базе данных вместо двух (хэш пароля + солевой хеш).

5 ответов

Решение

Соль, полученная исключительно из пароля, почти бессмысленна; Вы только что создали немного другую (но постоянную) хеш-функцию в целом. Единственная радужная таблица (пусть и пользовательская) может использоваться для целевого использования всей базы данных.

Кроме того, если соль получается из пароля, идентичные пароли отображаются как идентичные "хэши паролей". Простые пароли, скорее всего, будут отображаться как дубликаты - фактически вы создаете свою собственную радужную таблицу.

Весь смысл сохранения уникальной, независимо генерируемой соли для каждого пароля заключается в том, что каждый отдельный пароль хешируется с помощью уникальной хэш-функции. Поэтому не было бы ни одной радужной таблицы, которую можно было бы использовать по всей вашей базе данных.

Это только частично смягчит проблему. По сути, вы создали хеш-функцию с ключами.

С такой функцией общие радужные таблицы больше не будут применимы, но вы все еще в опасности, если злоумышленник захватит всю вашу базу данных. В этом случае он может создать новую радужную таблицу на основе этой случайной строки. Используя эту новую таблицу, он имеет высокий шанс взломать хотя бы одну учетную запись в вашей системе.

Добавление отдельной соли эквивалентно использованию другой хеш-функции для каждого пароля, поэтому вам потребуется отдельная радужная таблица для каждой возможной соли, что делает атаку чрезвычайно дорогой. Если злоумышленник создает радужную таблицу для одной соли, он может взломать только те пароли, которые солят.

Кроме того, я хотел отметить, что не имеет значения, сколько "статической" случайности вы добавляете, если случайность остается постоянной.

Нежелательная почта получена из пароля, поэтому она не имеет эффекта засолки, все равно можно создать радужную таблицу, которую можно применить ко всей таблице.

Но если вы хотите использовать только один столбец, ответ будет проще:

Просто используйте pbkdf2 для непосредственного создания хэша, создания 64-битной (8-байтовой) случайной соли, используйте большее количество итераций (4000-10000). Если ваш столбец может содержать только 160 бит (20 байтов), тогда генерируйте 12 байтов (если ваш столбец может содержать больше, я бы увеличил его до 24 байтов) и сохраните в вашем столбце соль + объединенный хеш.

Таким образом, когда вы сравниваете, вы просто считываете первые 8 байтов, чтобы получить соль.

Вы всегда можете взять текущие базы данных хэшированные пароли и снова хэшировать их с другим хэш-алгоритмом + соль, как если бы они были исходным паролем. Совершенно безопасно наслоить хеширование поверх хеширования, на самом деле это часто более безопасно. Имейте в виду, однако, что вы также должны быть в состоянии полностью изменить процесс, иначе вы все сломаете.

Реструктуризация базы данных для простого добавления поля соли - лучший вариант (или единственный, если вы собираетесь делать это правильно, но вы можете использовать поле хеша смородины для хранения соли, как написал другой человек)

когда они в следующий раз войдут в систему с действительным именем пользователя и паролем (поскольку это происходит только тогда, когда хэш пароля должен измениться, или если вы выполняете массовое принудительное изменение пароля на каждом из них), если нет данных Salt, тогда генерируйте большую случайную соль, сохраняйте ее и восстанавливайте хеш от пароля + соли (в идеале, используя что-то лучше, чем SHA1, pbkdf2 должен быть установлен выше, чем 10000, но зависит от ресурсов вашего сервера)

Другие вопросы по тегам