Гайд по хешированию паролей

Это вольный перевод статьи OWASP.

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

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

В этой статье подробно рассказано как безопасно хранить пароли. Если лень читать, то вот основные выводы:

  • Используйте Argon2id с минимальным конфигом в 15 MiB памяти, 2 итерациями и 1 степенью параллелизма.
  • Если Argon2id не доступен, используйте с scrypt с минимальной стоимостью 2^16, размером блока 8 и 1 в качестве значения параллелизации. 
  • Во всяких легаси используйте bcrypt, с рабочим фактором 10 и паролями не длиннее 72 байт.
  • Если делаете что-то для гос. сектора США и вам надо соответствовать FIPS-140, то используйте PBKDF2 с количеством итераций не менее 310,000 и HMAC-SHA-256 в качестве алгоритма хеширования.
  • Солите и перчите пароли.

Определяемся с терминами

Разница между хеширование и шифрованием

Что хеширование, что шифрование являются инструментами для защиты данных. Но, практически во всех случаях пароли нужно только хешировать, а не шифровать.

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

Шифрование же наоборот, обратимая операция. Подходит больше для случаев, когда данные должны использоваться в открытом виде (например, адрес доставки – если бы мы хранили только его хеш, то такая информация была бы бесполезной).

Пароли шифруют только в редких случаях. Например, если пароль нужно будет использовать для аутентификация в друг сервисах, которые не поддерживают более безопасные методы вроде OpenID Connect (OIDC). 

Подробнее про шифрование можно почитаться здесь.

Как «взломать» хеш

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

Это выглядит примерно так:

  • Придумать какой пароль мог бы использоваться (например, 1password1)
  • Получить его хеш
  • Сравнить полученный и украденный хеши. Если они совпадают, поздравляю – пароль найден.

Все эти шаги обычно использую, но в больших масштабах:

  • Могут быть использованы базы ранее утекших паролей
  • Прямой перебор всех возможных вариантов
  • Использование популярных паролей

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

Способы хранения пароля

Соль

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

Соль также защищает от использования предварительного сгенерированных хешей – так называемых радужных таблиц. Так же при использовании соли становится невозможным узнать используют ли пользователи одинаковые пароли.

Некоторые современные алгоритмы хеширование (Argon2id, bcrypt, and PBKDF2) автоматически добавляют соль к паролю, поэтому зачастую никаких дополнительных действий не требуется.

Перец

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

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

  • Перец переиспользуется между паролями, в отличие от соли.
  • Перец нельзя хранить в базе данных вместе с паролями.
  • Перец должны храниться в специальном хранилище ключей или HSMs (Hardware Security Modules).
  • Как и в случае с любым криптографическим ключом, нужно предусмотреть его механизм ротации.

Фактор работы 

Фактор работы это сколько итераций хеширования выполняется для пароля (обычно 2^фактор работы). Нужен, чтобы сделать вычисление хеша более ресурсно-затратным для злоумышленника. Фактор обычно хранится вместе с хешем.

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

Увеличение фактора работы

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

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

Алгоритмы хеширования паролей

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

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

На сегодняшний день существует три основных алгоритма:

Argon2id

Argon2 является победителем 2015 года в конкурсе «Password Hashing Competition». Существуют три версии алгоритма: Argon2i, который защищает от атак по сторонним каналам, Argon2d, устойчивый к GPU атакам и Argon2id объединяющий все вместе.

Взамен указания простого значения фактора работы, Argon2id кофигурируется тремя параметрами: минимальный размер памяти (m), минимальное количество итераций (t) и степень параллелизма (p).

  • m=37 MiB, t=1, p=1
  • m=15 MiB, t=2, p=1

Обе приведенные кофигурации дают хорошую защиту от аттак. Выбор только в том, какие ресурсы вы готовы зайдействовать больше – CPU или RAM.

scrypt

scrypt – алгоритм хеширования паролей, разработанный Колином Персиваль. Хоть Argon2id и является более предпочтительным алгоритмом, scrypt также может быть использован при правильной конфигурации. Например, в Node.js scrypt является частью ситемной библиотеки и может быть использован в сочетании с randomBytes для соли.

Так же как Argon2id, scrypt конфигурируется тремя параметрами: минимальной CPU/memory, стоимостью (N), размером блока (r) и степенью параллелизма (p)

  • N=2^16 (64 MiB), r=8 (1024 bytes), p=1
  • N=2^15 (32 MiB), r=8 (1024 bytes), p=2
  • N=2^14 (16 MiB), r=8 (1024 bytes), p=4
  • N=2^13 (8 MiB), r=8 (1024 bytes), p=8
  • N=2^12 (4 MiB), r=8 (1024 bytes), p=15

Все описанные конфигурации дают максимальную степень защиты – разница только в использовании CPU и RAM.

bcrypt

bcrypt следует использовать если ни один из предыдущих вариантов недоступен. При этом подбирайте настолько большой фактор работы, насколько позволяют ресурсы сервера, но не меньше 10.

Ограничение на длину пароля

bcrypt принимает на вход максимум 72 байта в большинстве случаев. Это нужно учитывать при выборе длины пароля (72 байте это не длинна строки, некоторые символы, такие как emoji, могут занимать больше 1 байта)

Предварительное хеширование 

Описанное выше ограничение можно обойти, если перед передачей пароля в bcrypt, предварительно хешировать его другим быстрым алгоритмом вроде SHA-256 (например, bcrypt(base64(hmac-sha256(data:$password, key:$pepper)), $salt, $cost)). Это популярная, но опасная практика из-за возможного «шакинга» пароля и других проблем связанных с комбинированием bcrypt с другими алгоритмами хеширования.

PBKDF2

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

PBKDF2 предоставляет выбор внутреннего алгоритма хеширования. HMAC-SHA-256 имеет хорошую поддержку и рекомендован NIST

Фактор работы PBKDF2 определяется количеством итераций.

  • PBKDF2-HMAC-SHA1: 720,000 итераций
  • PBKDF2-HMAC-SHA256: 310,000 итераций
  • PBKDF2-HMAC-SHA512: 120,000 итераций

Приведенные параметры гарантируют одинаковую степень защиты.

Если PBKDF2 используется одним из HMAC алгоритмов, и длина пароля больше размера блока (64 байта для SHA-256), пароль будет автоматически дополнительно предхеширован. Например, пароль "This is a password longer than 512 bits which is the block size of SHA-256" будет конвертирован в такой хеш fa91498c139805af73f7ba275cca071e78d78675027000c99a9925e2ec92eedd. В хороших реализациях PBKDF2, этап предхеширования будет исполнен до начала исполнения основного алгоритма. Но в некоторых реализациях, такое предхеширование происходит на каждой итерации, что приводит к значительному увеличению времени хеширования при использовании длинных паролей, например, как было в Django в 2013. В таком случает лучше делать ручное предварительное хеширование с солью.

Обновление легаси хешей

В старых приложениях могут использоваться менее защищенные алгоритмы хеширования вроде MD5 или SHA-1, поэтому их важно заменить на более современные и защищенные способы хеширования.

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

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

Альтернативным подходом может быть перехеширование всех хешей с новым алгоритмом. Например было md5($password), а стало bcrypt(md5($password)). Недостатки такого подхода уже описывались выше.

Чтобы как-то нивелировать недостатки описанных проблем, вы можете комбинировать добавление дополнительного уровня хеширования с просьбой сменить пароль при следующем логине.

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

Международные символы

Также советуем убедиться в том, что выбранный алгоритм хорошо работает с большим количеством символов и совместим с Unicode. Пользователи не должны быть ограничены в выборе символов, которые они могут использовать для своих паролей. Также учитывайте, то что в пароле может содержаться NULL байт.