Алгоритмы хеширования паролей: что выбрать и почему это важно
Выбор алгоритма хеширования напрямую влияет на то, насколько сложно злоумышленнику восстановить пароль из базы данных — даже если он получит к ней доступ. Ниже — сравнение популярных алгоритмов с практическими рекомендациями.
| Алгоритм | Рекомендация и особенности |
|---|---|
| bcrypt | Один из лучших вариантов для хранения паролей: медленный по дизайну, имеет встроенную соль, хорошо противостоит брутфорсу. Используйте cost factor от 10 до 12 для баланса скорости и защиты. |
| Argon2id | Победитель Password Hashing Competition 2015 года. Рекомендован OWASP как первый выбор для новых проектов — устойчив к атакам на GPU и FPGA. |
| scrypt | Хорош там, где нужна высокая нагрузка на память. Сложнее в настройке, чем Argon2, но отлично подходит для блокчейн-приложений и систем с повышенными требованиями к безопасности. |
| PBKDF2 | Широко поддерживается и принят NIST. Подходит для enterprise-среды и совместимости с FIPS, но менее устойчив к GPU-атакам, чем Argon2. |
| SHA-256 без соли | Категорически не рекомендуется для паролей: слишком быстрый, уязвим для rainbow table атак. Подходит только для хеширования файлов и данных, не паролей. |
| MD5 | Устарел для любых задач безопасности. Коллизии давно известны, тысячи MD5-хешей расшифровываются за секунды через публичные базы. Не используйте. |
| SHA-1 | Взломан ещё в 2017 году (атака SHAttered от Google). До сих пор встречается в старых системах — обязательно мигрируйте на современные алгоритмы. |
| bcrypt + pepper | Усиленный вариант: «перец» — это секретный ключ, добавляемый к паролю до хеширования и хранящийся отдельно от базы. Даже при утечке БД хеши будут бесполезны без него. |
| Argon2d | Версия Argon2, оптимизированная против атак через side-channel. Однако для большинства веб-приложений лучше выбирать Argon2id — он сочетает защиту обоих вариантов. |
| Двойное хеширование (MD5+SHA) | Распространённое заблуждение, что это добавляет защиту. На практике — увеличивает сложность кода без реального выигрыша в безопасности. Лучше один раз, но правильно. |
| Hmac-SHA256 | Хорошо подходит для верификации целостности данных и API-токенов, но не для хранения паролей — слишком быстрый. Полезен в паре с другими механизмами защиты. |
Частые ошибки при хранении и обработке паролей
Большинство утечек паролей происходит не из-за слабых алгоритмов, а из-за банальных ошибок в реализации — некоторые из них совершают даже опытные разработчики.
| Ошибка | Почему это опасно и как исправить |
|---|---|
| Хранение пароля в открытом виде | При утечке базы все пользователи скомпрометированы мгновенно. Хешируйте пароль ещё на сервере перед любой записью. |
| Использование одной соли для всех паролей | Если злоумышленник узнает соль, он строит одну rainbow table и ломает все пароли разом. Генерируйте уникальную случайную соль для каждого пользователя. |
| Слишком короткая соль | Соль менее 16 байт существенно снижает защиту. Стандарт — минимум 16 байт случайных данных, лучше 32. |
| Логирование паролей | Пароли попадают в логи через отладочный вывод или логирование входящих запросов. Обязательно фильтруйте поля password/token во всех логерах. |
| Передача пароля в GET-параметрах | GET-параметры сохраняются в логах сервера, истории браузера и заголовках Referer. Всегда используйте POST для форм с паролем. |
| Сравнение хешей обычной строковой функцией | Обычное сравнение строк уязвимо к timing-атакам: по времени ответа можно угадать хеш посимвольно. Используйте constant-time comparison (например, hash_equals в PHP). |
| Ограничение длины пароля (например, до 20 символов) | Это подсказывает, что пароль хранится в открытом виде или шифруется, а не хешируется. Хеш имеет фиксированную длину — ограничивать вход не нужно. |
| Хеширование на стороне клиента вместо сервера | Если клиент передаёт хеш, а сервер его и хранит — хеш по сути становится паролем. Хешируйте только на сервере, а передачу защищайте через HTTPS. |
| Не обновлять алгоритм при входе пользователя | Если вы перешли на более новый алгоритм, обновляйте хеш старых пользователей при каждом успешном входе — это безопасный и постепенный способ миграции. |
| Игнорирование скорости хеширования | Если операция хеширования занимает меньше 100 мс — это слишком быстро для паролей. Настройте cost/iteration factor так, чтобы одна операция занимала 200–500 мс на вашем сервере. |
| Хранение хеша и соли в разных, но связанных таблицах | Это лишь иллюзия защиты: при JOIN-запросе всё равно получается полная связка. Соль лучше хранить прямо в поле хеша (bcrypt и Argon2 делают это автоматически). |
| Отсутствие rate limiting на endpoint аутентификации | Без ограничения запросов злоумышленник может перебирать пароли напрямую через API. Добавьте блокировку после 5–10 неудачных попыток с экспоненциальной задержкой. |
Практические советы по построению надёжной системы аутентификации
Безопасная аутентификация — это не только правильный алгоритм хеширования, но и целый набор мер, которые работают вместе. Эти советы помогут выстроить систему, устойчивую к реальным атакам.
| Совет | Как правильно реализовать |
|---|---|
| Внедрите двухфакторную аутентификацию | Даже идеально захешированный пароль не поможет, если его украдут через фишинг. TOTP (Google Authenticator, Authy) — минимальный стандарт для любого сервиса с чувствительными данными. |
| Проверяйте пароли по базам утечек | API сервиса HaveIBeenPwned позволяет проверить, не фигурирует ли пароль в известных утечках — без передачи самого пароля, только первые 5 символов хеша. |
| Установите минимальную длину пароля в 12 символов | Исследования NIST показывают, что длина пароля важнее его сложности. Пароль «правильная лошадь скрепка батарея» надёжнее, чем «P@ss1!» из 6 символов. |
| Не требуйте регулярной смены пароля без причины | Принудительная смена каждые 90 дней приводит к тому, что пользователи выбирают предсказуемые вариации. Меняйте пароль только при подозрении на компрометацию. |
| Храните сессионные токены отдельно от паролей | После входа выдавайте пользователю токен с коротким TTL. Компрометация токена изолирована и не раскрывает пароль. |
| Используйте HTTPS везде, не только на странице входа | Если страница профиля или API-запросы идут по HTTP, сессионный cookie может быть перехвачен в сети. Перенаправляйте весь трафик на HTTPS принудительно. |
| Настройте флаги безопасности для cookies | Флаги HttpOnly (защита от XSS) и Secure (только HTTPS) должны быть установлены для всех cookies, связанных с аутентификацией. SameSite=Strict защищает от CSRF. |
| Логируйте подозрительные входы | Вход с нового устройства, страны или IP-адреса — повод отправить уведомление пользователю. Это дешёвая и эффективная защита от угона аккаунтов. |
| Реализуйте безопасное восстановление пароля | Ссылка для сброса должна быть одноразовой, иметь срок жизни не более 1 часа и привязываться к конкретному IP или устройству. После использования — немедленно инвалидировать. |
| Не подсказывайте, существует ли аккаунт | Ответ «такой email не зарегистрирован» помогает злоумышленникам перечислять пользователей. Возвращайте одно нейтральное сообщение и при ошибке входа, и при отсутствии аккаунта. |
| Тестируйте систему аутентификации отдельно | Напишите автоматические тесты, которые проверяют: невозможность входа с неверным паролем, блокировку после N попыток, корректную инвалидацию токенов при выходе. |
| Следите за обновлениями используемых библиотек | Уязвимости в популярных пакетах (например, passport.js, bcrypt.js) закрываются патчами. Настройте автоматическое обновление зависимостей или хотя бы еженедельный аудит через npm audit / composer audit. |
| Документируйте алгоритм и параметры хеширования внутри проекта | Через год никто не вспомнит, почему выбран именно cost factor 11. Фиксируйте решения в README или ADR-файлах — это упрощает миграцию и аудит безопасности. |