Про хранение паролей в БД
Сегодня посмотрим, как лучше всего хранить пароли в базе данных и как известные платформы решают эту задачу.
Plaintext
Когда встал вопрос хранения паролей, конечно, первой идеей было просто записывать их в открытом виде в соответствующей табличке в базе данных. И все бы ничего, если бы доступ к ней действительно напрямую клиенты получить не могли. Но, к сожалению, в различных веб-приложениях по-прежнему иногда работает такая известная всем SQL-инъекция, не говоря уже о других потенциальных уязвимостях. В вопросах безопасности вообще принято предполагать худшее и готовить план действий и защиту даже на такой случай. Будем считать, что злоумышленник нашел в веб-приложении лазейку, тем или иным способом радостно выгружает себе таблицу с именами и паролями пользователей и дальше уже распоряжается ими, как ему вздумается. В общем случае его дальнейшие действия могут быть следующими:
- выполнение нелегитимных действий от имени пользователей с использованием их учетных данных на уязвимом ресурсе: например, к учетной записи привязана банковская карта, и теперь злоумышленник может ей пользоваться;
- попытка использования полученного пароля на других ресурсах: далеко не всегда пользователи, следуя советам, придумывают каждый раз новые пароли для разных сервисов;
- попытка выявить правило генерации пароля и перейти ко второму пункту: некоторые формируют какое-то правило составления пароля, в итоге на разных ресурсах пароли разные, но подчиняются одному и тому же правилу, которое можно выявить;
- повышение привилегий: в той же таблице может храниться и пароль администратора, со знанием которого иногда можно получить полный контроль над сервером.
Шифрование Хэширование
Идея сразу оказывается не такой хорошей. Что делать? Здорово было бы хранить пароли в зашифрованном виде. Тогда, даже если их извлекут, восстановить не смогут или, по крайней мере, потратят на это слишком много времени. Здесь выбор встает между двумя ветками развития: шифровать пароли или хэшировать. Разработчики остановились на втором, и, в принципе, понятно, почему. Сравним наших претендентов по разным характеристикам:
- Трудоемкость. Шифрование занимает больше времени, а какое преобразование мы бы ни выбрали, его придется проделывать при каждой проверке пароля. Одним из требований к хэш-функциям же является быстрота выполнения.
- Длина выходных значений. Результат шифрования имеет переменную длину, результат хэширования — всегда одинаковую, а хранить однородные по размеру данные в базе данных очень уж удобно. Не говоря уже о том, что длина пароля в зашифрованном виде будет давать некоторую информацию о длине исходного пароля. Одинаковая длина, правда, приводит к возможности возникновения коллизий, но об этом ниже.
- Управление ключами. Для шифрования требуется ключ, который тоже где-то придется хранить и надеяться, что его никто не найдет. В любом случае, генерация и управление ключами это отдельная история (они не должны быть слабыми, их нужно регулярно менять и так далее).
- Возможность коллизии. При шифровании выходные данные от различных входных даных всегда тоже будут различны. При хэшировании же это не всегда так. Постоянная длина хэша означает ограниченность множества выходных значений хэш-функции, что приводит к возможности коллизии. То есть, допустим, пользователь действительно заморочился и придумал себе по-настоящему классный длинный пароль, в котором есть и спецсимволы, и цифры, и буквы в нижнем и верхнем регистре. Злоумышленник вводит в поле пароля не менее классный пароль «admin». Сервер для проверки и сравнения хэшей захэшировал его. Хэши совпали. Обидно.
Таким образом, со счетом 3:1 побеждает хэширование. Но можно ли на этом остановиться?
Ответ: нет.
Атаки на хэшированные пароли
Итак, злоумышленник заполучил нашу таблицу с именами пользователей и паролей. Пароли теперь захэшированы, но это нашего атакующего не останавливает, и он всерьез намерен их восстановить. Его возможные действия:
- брутфорс по словарю: если с эталонным паролем администраторов у злоумышленника ничего не вышло, он обратится к словарю популярных паролей и попытает счастья с их хэшами;
- радужные таблицы: вообще сегодня ему, может, не надо будет совсем ничего вычислять и перебирать по словарю. Достаточно будет обратиться к лежащим в сети радужным таблицам. В радужных таблицах содержатся уже вычисленные кем-то до этого хэш-значения и соответствующие им входные данные. Важно отметить, что в силу коллизий, пароль, который предложит радужная таблица, не обязательно будет именно тем, который использует пользователь. Предвычисленные значения есть уже для MD5, SHA1, SHA256, SHA512, а также для их модификаций и некоторых других. Попробовать обратить хэш можно, например, здесь;
- полный перебор: если не поможет и это, придется прибегнуть к брутфорсу и перебирать подряд все возможные пароли, пока вычисленные хэши наконец не совпадут.
В самом общем случае злоумышленнику придется брутить пароли. И тут его успех будет зависеть в том числе от быстроты вычисления хэш-функции. Сравнение по времени работы хэшей можно посмотреть здесь. Например, реализованные на Java хэш-функции на 64-битной Windows 10 с 1 core Intel i7 2.60GHz и 16GB RAM были запущены по миллиону раз для вычисления хэша длины в 36 символов. Они показали следующие результаты:
MD5 — 627 мс
SHA-1 — 604 мс
SHA-256 — 739 мс
SHA-512 — 1056 мс
А ведь сегодня брутфорс можно распараллелить и выполнить в разы быстрее на GPU (а также на APU, DSP и FPGA). Однако помимо выбора более долгого алгоритма и более длинного выходного результата можно сделать кое-что еще.
Хэширование хэша
Чтобы помешать нарушителю воспользоваться готовыми радужными таблицами, существует техника хэширования пароля несколько раз. То есть вычисляем хэш от хэша от хэша от хэша… и так n раз (надо, правда, сильно с этим не увлекаться, потому что при обычной проверке пароля пользователя серверу тоже придется это проделывать). Теперь так просто по радужной таблице он пароль не найдет, да и время на брутфорс заметно увеличится. Но ничто не остановит злоумышленника от того, чтобы сгенерировать радужную таблицу по словарю паролей, зная алгоритм хэширования. Тем более, для самых популярных комбинаций этого метода такие таблицы уже сгенерированы:
»
Добавить соль по вкусу
Для того, чтобы и это он не смог сделать, пароли сегодня хэшируются с добавлением соли.
Соль — это дополнительная случайная строка, которая приписывается к паролю и хэшируется вместе с ним. Из полученного таким образом хэша по радужной таблице пароль уже не восстановишь. Зная соль и выходной хэш, злоумышленник обречен на брутфорс и никакие заранее вычисленные таблицы ему, скорее всего, не помогут.
Таксономия соления паролей:
1. По принципу соления:
- уникальная соль для каждого пользователя: индивидуальная для каждого пользователя — таким образом, если соль станет известна злоумышленнику, брутить придется пароль каждого по отдельности. И кроме того, даже если два пользователя мыслят одинаково и придумали идентичные пароли, хэши все равно на выходе будут разными;
- глобальная соль: одинакова для всех, используется для всех хэшей;
- и то, и другое.
2. По методу хранения соли:
- в базе: как правило, индивидуальные соли хранятся в той же базе, что и хэши паролей; часто даже в той же строке;
- в коде (читать: в конфиге): глобальную соль обычно хранят не в базе данных, а, например, в конфиге, чтобы нарушителю пришлось потратить время на ее подбор.
Будем считать, что индивидуальные соли пользователей хранятся в базе, глобальная соль в конфиге. Злоумышленник получил доступ к базе, и ему известны все хэши и соответствующие им соли (глобальная соль хранится не в базе, и ее он не знает). Итого, если объединить все способы, то для того, чтобы получить пароли в открытом виде, как было в первых системах, он, будучи крайне целеустремленным, столкнется со следующими препятствиями:
- Ему неизвестна глобальная соль, поэтому ее придется брутить.
- Ему известны соли пользователей, но заготовленных таблиц с этими солями у него нет, поэтому пароли придется брутить.
- Процесс этот займет еще больше времени из-за того, что придется хэшировать хэши по n раз.
Как хранят пароли различные CMS
Wordpress
До версий 3.х пароли просто хэшировались с помощью MD5. Сейчас используется библиотека phpass. По умолчанию к паролю спереди приписывается соль и полученная строка хэшируется MD5 2^8 раз.
Joomla
До версии 1.0.12 использовался просто MD5. Используется библиотека phpass, по умолчанию bcrypt с солью и 2^10 повторениями.
Drupal
До версии 6 md5 без соли. Используется библиотека phpass. По умолчанию соленый sha512 с 2^16 повторениями.
Silverstripe
Использует соленый Blowfish c 2^10 повторениями.
Umbraco
Использует HMACSHA256 с солью. Использует вторую, глобальную соль, задаваемую в конфиге.