АрхитектураАрхитектура безопасностиРазработчик серверных приложений

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

Сервис хранит пароли следующим образом:

def register(password):
    db.save(hash=sha256(password.encode()).hexdigest())

def login(password, saved_hash):
    return sha256(password.encode()).hexdigest() == saved_hash

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

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Нужно хранить не быстрый хеш пароля, а результат работы медленной ресурсоёмкой функции выработки ключа: например, Argon2id, scrypt, bcrypt или настроенного PBKDF2. Для каждого пароля используется уникальная случайная соль, а вместе с результатом сохраняются соль и параметры функции.

Соль не является секретом: она мешает заранее подготовленным таблицам и заставляет атакующего подбирать каждый пароль отдельно. Ресурсоёмкость замедляет массовые офлайн-проверки паролей после утечки базы.

Исторический контекст

Ранние системы иногда хранили пароли открыто или применяли обычные быстрые хеш-функции. Такой подход был удобен для проверки пароля, но после компрометации базы позволял быстро перебирать огромные наборы распространённых паролей на CPU и GPU.

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

Постановка проблемы

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

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

Подробное решение

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

import hashlib, hmac, secrets def derive(password, salt): return hashlib.scrypt(password.encode(), salt=salt, n=2**14, r=8, p=1) def register(password): salt = secrets.token_bytes(16) return salt, derive(password, salt) def verify(password, salt, stored): return hmac.compare_digest(derive(password, salt), stored)

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

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

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

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

Ситуация из практики

У интернет-сервиса произошла утечка базы с быстрыми хешами. Рассматривались три варианта. Открытое хранение или обратимое шифрование были отвергнуты: они не обеспечивают достаточной защиты при раскрытии данных или ключа. Простая замена SHA-256 на SHA-512 тоже не решала проблему, поскольку это по-прежнему быстрый хеш.

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

Такое решение не делает слабые пароли безопасными: их всё ещё можно подобрать перебором. Оно лишь увеличивает стоимость каждой проверки и не позволяет эффективно переиспользовать результаты подбора между пользователями.

Что кандидаты часто упускают

  1. Вопрос: Почему одной соли недостаточно для защиты слабого пароля?

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

  2. Вопрос: Как безопасно увеличить стоимость хеширования без одновременного сброса всех паролей?

    Ответ: В записи сохраняют версию алгоритма и его параметры. При входе сначала проверяют пароль со старыми параметрами, а после успешной проверки пересчитывают результат с актуальными и заменяют запись. Для давно неиспользуемых учётных записей может потребоваться отдельный сброс пароля, поскольку без правильного пароля пересчитать хеш невозможно.

  3. Вопрос: Как утечка базы меняет оценку риска, если применяется pepper?

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