Хэширование паролей


Поскольку большая часть программистов не понимает, как работает защита паролей с помощью хэширования и как её правильно применять, то я решил составить небольшой поясняющий текст на эту тему.

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

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

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

Каким типам атак может быть подвержен хэш пароля?

1. Поиск хэша по "радужным таблицам" (rainbow tables): огромным базам данных, где собираются заранее посчитанные хэши для любых возможных строк.
2. Метод грубой силы (bruteforce): перебирать все комбинации символов и прменять к ним хэширующую функцию до тех пор, пока она не вернёт искомый хэш.
3. Поиск по словарю. Похож на брутфорс, но перебор не всех возможных значений, а всего нескольких тысяч самых популярных паролей, типа "123", "password" и пр.

Для защиты от этих трех типов атак требуются разные механизмы. Это:

1. Для защиты от поиска про радужным таблицам была придумана соль (salt): строка из случайных символов, добавляемая к паролю перед вычислением хэша. Таким образом мы удлиняем пароль до такого состояния, что никакая радужная таблица не сможет вместить всех вариантов.
Недостаток соли состоит в том, что она должна храниться вместе с паролем в открытом виде. И таким образом делает пароль уязвимым для двух других типов атак. Но следует всегда отдавать себе отчёт в том, что это так и задумано - соль предназначена для того, чтобы быть скомпрометированной вместе с хэшем. И защита строится исходя из того факта, что соль заведомо известна атакующему.

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

3. Перебор по словарю (а так же в эту категорию можно записать брутфорс для относительно коротких паролей, для подбора которых понадобится перебор сравнительно небольшого количества вариантов) - самая неприятная атака. Именно из-за неё вас мучают некоторые сайты, привередничая в выборе пароля "слишком легкий", "слишком повторяющийся" и пр. Но делают они это для вашей пользы. Сложный пароль нужен для того, чтобы его нельзя было подобрать по словарю, и чтобы брутфорс по хэшу занял неприемлемо долго время. (Кстати, не стоит обольщаться, используя якобы стойкий пароль вида gfhjkm - слово "пароль" в латинской раскладке - хакер тоже не дурак, и обязательно попробует слова из словаря в в другой раскладке)

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

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

Перейдём теперь к конкретике.

1. Соль. Самый простой пункт: генерируется автоматически алгоритмом из п.2
2. Алгоритм. http://www.php.net/manual/ru/book.password.php . Если у вас ископаемая версия РНР - там же есть ссылка на реализацию на чистом РНР.
3. Пароль. В настоящее время можно считать стойким пароль из 12 символов, включающих цифры и буквы разного регистра или кодовую фразу, состоящую из 4-5 не слишком употребительных слов.