Модификация алгоритма хэширования
Опубликовано 15 февраля 2008, автор: Иван Блинков
Если Вы уже успели прочитать одну из моих предыдущих записей о хэшировании, то Вы уже имеете базовое представление о теме сегодняшнего разговора.
Одним из возможных способов применения хэшей является хранение аутентификационных данных пользователей интернет-приложения, об особенностях реализации формирования и проверки хэшей при регистрации и авторизации пользователей средствами PHP я и хотел бы с Вами поговорить.
Сомневаюсь, что Вы услышите что-то новое, если я скажу, что в PHP даже в «стандартной комплектации» реализована масса алгоритмов хэширования, начиная с широкораспространенных md5 (); и sha1 (); и заканчивая модулями hash и mhash, в которых реализована еще целая масса алгоритмов. Все они давно уже стандартизованы и доступны для изучения всем желающим получить о них какую-либо информацию.
Допустим мы храним пароли пользователей в виде какого-то стандартного хэша, для примера — md5, в базе данных. Все было отлично, но в один прекрасный момент нашелся подлый злоумышленник, который неким хитрым способом получил доступ к базе данных логинов и паролей. Перед ним стоит цель — узнать изначальный пароль у максимального числа пользователей. Посмотрим на ситуацию с его стороны:
- Первым делом он бы попытался определить, какой именно хэш перед ним находится — чаще всего это делается либо просто взглянув на длину хэша, либо если приложение широко распространено (популярная CMS скажем) — покопавшись в ее исходниках, еще есть вариант найти свой собственный аккаунт — и зная пароль попробовать на нем разные алгоритмы, способов можно придумать множество — все ограничивается лишь воображением. Узнав ответ на свой вопрос ему лишь останется набрать в Google фразу вроде «md5 decrypt», а дальше уже дело техники.
- Еще один вариант решения задачи — взглянуть на список хэшей на предмет наличия совпадений. С очень высокой степенью вероятности за значительной группой одинаковых хэшей будет скрываться какой-либо банальный пароль вроде 123456.
Задача же разработчика приложения максимально обезопасить систему от подобных ситуаций. Конечно же можно просто стараться минимизировать возможности реализации методов получения информации из базы данных, но предугадать все варианты невозможно: в любом из используемых компонентов системы может оказаться уязвимость в коде, на которую наверняка найдется умник, который напишет exploit, а значит полностью исключить такую вероятность не получится, в лучшем случае выйдет просто ее минимизировать.
Именно по этим причинам и стоит задуматься об усложнении задачи злоумышленника в случае возникновения описанной выше ситуации. Для исключения возможности просто расшифровывания хэшей по словарю (то есть первый случай, когда определяется тип хэша и соответствующий ему словарь хэш => исходное значение) достаточно исключить возможность идентификации алгоритма хэширования или наличия к нему заранее подготовленного словаря. Для этого достаточно лишь сделать шаг в сторону от стандартного алгоритма любым пришедшим в голову способом, например:
- хранить хэш не от самого пароля, а от пароль + какая-либо фиксированная строка
- поменять местами группы символов в получившемся стандартном хэше
- сделать сдвиг символов в стандартном хэше (или можно даже не сами символы двигать, а с помощью битовых операций их значения)
- комбинировать два стандартных алгоритма хэширования, или алгоритм хэширования с алгоритмом обратимого шифрования, которых доступно также множество
Список этот можно было бы продолжать достаточно долго, это было лишь первое, что пришло мне в голову. Но ни один из приведенных способов не избавит от возможности второго варианта раскрывания исходного пароля. Основывается он на однозначности стандартных алгоритмов — одним и тем же исходным данным соответствует один и тот же хэш. Для отказа от этого свойства стандартных алгоритмов придется выполнить более сложную модификацию используемой для генерации хэша функции (которая конечно же тоже поможет и для борьбы с первым вариантом). Сразу приведу пример кода, реализующего этот механизм, а дальше попытаюсь его объяснить:
function generateHash($input,$salt = false)
{
if(!$salt)$salt=randomString(2);
$hash=md5($input.$salt);
return $salt.substr($hash,2);
}
Как не трудно заметить — используется самодельная функция randomString ();, которая возвращает случайную строку, состоящую из указанного количества шестнадцатеричных цифр (надеюсь Вы в состоянии написать ее своими силами). Именно этот момент и гарантирует элемент случайности при каждой новой генерации хэша. В том месте, где я прочитал про этот механизм (ссылку, к сожалению, привести не могу — в bookmark'ах не нашел), этот случайный компонент назывался словом salt, смысл его заключается в том, что он приписывается ко входным данным, передаваемым стандартной функции хэширования, а затем им же подменяется какая-либо фиксированная часть полученного хэша.
Наверняка у Вас возник вопрос: а как же потом понять, что пользователь ввел верные данные, ведь для тех же исходных данных получится другой хэш и возможности их сравнить не будет? Ответ достаточно прост, его можно было увидеть даже в коде: при повторной инициализации хэша из базы данных достается заранее известная часть хранящегося там хэша, соответствующего конкретному пользователю — тот самый salt, и передается нашей функции. В этом случае в механизме будет использоваться именно он, а не новое случайное значение, и, как следствие, в случае правильности введенных данных на выходе получатся совпадающие хэши. Вот такой вот простенький, но иногда достаточно полезный трюк.
Если Вам понравился этот пост — возможно Вам придутся по душе и остальные записи из этой серии статей, а не пропустить публикацию новых записей Вам может помочь RSS feed.

13 комментариев на запись “Модификация алгоритма хэширования”
второй вариант решается, причем успешно, хешированием комбинации логин-пароль
[quote comment="173"]второй вариант решается, причем успешно, хешированием комбинации логин-пароль[/quote]Соглашусь, еще один вариант решения этого вопроса, имеющий право на существование.
Интересно, на сколько снижается криптоскойкость того же md5 если мы знаем часть строки?
И еще, как мне кажется, обрезая хэш на n символов (длину соли) мы увеличиваем вероятность коллизии в 2^n раз, так что лучше не укорачивать хэш, а увеличивать его на размер solt.
[quote comment="175"]Интересно, на сколько снижается криптоскойкость того же md5 если мы знаем часть строки?
[/quote]Знание части исходной строки явно сильно упрощает подбор по словарю, но каким образом это может повлиять на поиск коллизий в md5 — не представляю, по-моему никак.
[quote comment="175"]И еще, как мне кажется, обрезая хэш на n символов (длину соли) мы увеличиваем вероятность коллизии в 2^n раз, так что лучше не укорачивать хэш, а увеличивать его на размер solt.[/quote]Изменение длины хэша на, допустим, два символа может выдать сам факт использования этого метода, а если выполнить подмену — хэш внешне ничем не будет отличаться от стандартного.
Ну если у нас увели базу данных, то расчитывать на то, что не уведут код я бы не стал. Тем более я не доверяю методу защиты через сокрытие алгоритмов. (комментарий #3 — это я)
[quote comment="179"]Ну если у нас увели базу данных, то расчитывать на то, что не уведут код я бы не стал. Тем более я не доверяю методу защиты через сокрытие алгоритмов. [/quote]
Если еще учесть возможность получения несанкционированного доступа к коду, то да, увеличение длины хэша было бы более эффективным, но правда в этом случае возникла бы еще целая масса проблем и помимо раскрытого алгоритма хэширования.
[quote comment="179"](комментарий #3 — это я)[/quote]Спасибо, что представились, подписал его.
Выглядит несколько сомнительно. Если злоумышленник не знает алгоритма, то да — использование стандартных декрипторов мало чем ему поможет. Но с другой стороны, такие преобразования увеличивают число коллизий в среднем в 16^n раз, где n- длина рандомной строки. А это уже критично, если взломшик поимел доступ к коду.
кстати, в некоторых CMS используется подобный алгоритм. Например движок для формов VBulletin. Там двойной md5 с солью md5 (md5 ($password) . $salt)
В PunBB 1.3 используется такой же прием:
$form_password_hash = sha1 ($salt.sha1 ($form_password));
salt хранится в таблице «пользователи». честно говоря, сомнительное повышение защиты.
Извините, может я запутался, но какой смысл по большому счёту в $salt если он хранится в базе данных? И будет доступен злоумышленнику при взломе.
@Xyz Смысл в том, что не видя алгоритма какая именно строка подставляется злоумышленник не будет знать что использовать. Взлом может быть частичным — с помощью sql injection хакер получил записи из базы, но не видит кода php-скриптов.
PS Странно переписываться в комментах через полтора года после оригинального поста
VK
ну это, конечно, понятно, то есть не прямой доступ к таблам
PS на ixbt и через 4 года следующие ответы бывали )
Привет знатокам! Пишу cms не из тех побуждений, что люблю изобретать велосипеды. Во первых у меня студия веб дизайна, и честно устал от левого кода и левых ошибок, пишу что то модульное и гибкое для меня! Во вторых это отличный способ поднять уровень, проблем было нереальное количество, приходиться думать и об защите, пароль храню в куках в 2йном md5 не рассчитываю на взлом движка, но кража кук в полне может быть, придумал адский алгорит, скрестить 2й/md5 с солью, и тремя кодами движка!
Сам часто ворую хеш )) и понимаю основные ошибки, почему так выходит, 2й/md5 к примеру поставил меня на одном движке в тупик )