bcrypt vs Argon2 vs scrypt: хеширование паролей в 2026 году
Хеширование ≠ шифрование. Хеш — необратимый отпечаток фиксированной длины: ни сервер, ни атакующий не способны восстановить исходный пароль. Шифрование, напротив, обратимо при наличии ключа. Для паролей всегда применяется хеширование, никогда не шифрование — это базовое требование, на котором стоит вся остальная аргументация ниже. Если на собеседовании или в архитектурном ревью вы видите фразу «зашифрованные пароли в БД» — это инженерный red flag, не лингвистическая придирка.
Короткий ответ: для любого нового проекта в 2026 году используйте Argon2id с m=19456, t=2, p=1. Это совпадает с базовой рекомендацией OWASP Password Storage Cheat Sheet и даёт лучшую устойчивость к GPU и побочным каналам, какую можно отгрузить сегодня.
Если Argon2 нет в вашем стеке (редко, но бывает на встроенных или старых рантаймах), берите scrypt с N=2^17, r=8, p=1. bcrypt с cost=12 — только тогда, когда вы заперты в legacy-системе, которая уже умеет bcrypt, и добавить новую зависимость нельзя. PBKDF2-HMAC-SHA-256 с 600 000 итераций — когда обязательно соответствие FIPS-140.
| Алгоритм | Параметры OWASP 2026 | Когда выбирать |
|---|---|---|
| Argon2id | m=19456 KiB, t=2, p=1 | По умолчанию для новых проектов |
| scrypt | N=2^17, r=8, p=1 | Когда Argon2 недоступен |
| bcrypt | cost=12 (мин 10) | Только legacy-системы |
| PBKDF2 | HMAC-SHA-256, 600k итераций | Требуется FIPS-140 |
Дальше в статье — почему именно эти числа, как настроить их под ваше железо и как мигрировать без принудительного сброса паролей. Если нужны крепкие тестовые пароли для бенчмарков, возьмите генератор случайных паролей. Для общей картины — руководство по основам веб-безопасности.
Чем хеширование паролей отличается от обычного хеширования
Снаружи хеш-функции выглядят одинаково: на входе — данные, на выходе — отпечаток фиксированной длины, обратить нельзя. Но цели проектирования у «вычислить хеш ISO-образа на 4 ГБ» и «вычислить хеш 12-символьного пароля» тянут в противоположные стороны. Один должен работать так быстро, как позволяет кремний. Другой — так медленно, как позволяет бюджет задержки на логин.
Перепутывание этих двух задач — и есть тот канал, по которому утечка базы превращается в захват аккаунтов.
Почему MD5 и SHA-256 не подходят для паролей
Универсальные хеши вроде MD5, SHA-1 и SHA-256 строились ради пропускной способности. Они дают гигабайты в секунду на обычном CPU и десятки гигабайтов в секунду на GPU. Это отлично для контрольных сумм файлов и контент-адресации, и катастрофично для паролей.
Бенчмарки Hashcat на одной RTX 4090 в 2024-м показывают примерно 164 ГГц/с для MD5 и 22 ГГц/с для SHA-256. Восьмисимвольный пароль «нижний регистр + цифры» (36^8 ≈ 2,8 × 10^12 кандидатов) ложится одной GPU меньше чем за минуту против MD5 и за пару минут против SHA-256. База с sha256(password) — фактически plaintext.
Соль не спасёт. Она блокирует предвычисленные радужные таблицы, но никак не замедляет атаку по одному аккаунту: атакующий просто вычисляет хеш каждого кандидата с утёкшей солью.
Для не-security контрольных сумм MD5 и SHA-256 свою работу делают; для этого и есть инструменты вроде общего генератора хешей. Развёрнутое сравнение «когда какой алгоритм уместен» — в сравнении MD5 и SHA-256. Но для паролей нужен хеш, который специально работает медленно.
Что обязан делать современный хеш пароля
Хеш пароля, достойный отгрузки в 2026-м, имеет три свойства:
- Намеренно медленный, с настраиваемым work factor. Логин должен занимать 100–500 мс: достаточно быстро, чтобы пользователь не замечал, достаточно медленно, чтобы offline-атакующий тратил дни на миллион попыток. Work factor должен быть параметром, чтобы его можно было поднимать по мере улучшения железа.
- Соль на запись. Уникальная случайная соль на каждый пароль ломает радужные таблицы и заставляет атакующего бить по каждому аккаунту отдельно. Современные алгоритмы сами генерируют и встраивают соль в выходную строку.
- Memory-hard. GPU и ASIC быстры в вычислениях, но дороги в высокопропускной памяти. Алгоритм, требующий десятки МБ на хеш, заставляет атакующего обеспечивать RAM пропорционально параллелизму, что убивает экономику GPU-ферм.
bcrypt вытягивает (1) и (2), но не (3). scrypt — первый алгоритм, попавший во все три. Argon2 уточнил дизайн и выиграл Password Hashing Competition. Дальше разбираем каждый.
Три алгоритма: архитектура и компромиссы
bcrypt: на основе Blowfish, time-hard
bcrypt спроектирован в 1999-м Нильсом Провосом и Дэвидом Мазьером для OpenBSD. Построен на шифре Blowfish с дорогостоящей фазой инициализации ключа («EksBlowfish»), повторяемой 2^cost раз. Единственный настраиваемый параметр — cost factor (он же «log rounds»): каждое приращение удваивает работу. cost=10 делает 1024 раундов установки ключа; cost=14 — 16 384.
Хеш bcrypt выглядит так:
$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
│ │ │ │
│ │ │ └─ 31-символьный base64 хеш
│ │ └─ 22-символьная base64 соль
│ └─ cost factor (12)
└─ идентификатор алгоритма ($2b$ = bcrypt v2)
Формат самоописывающий: verify() читает cost и соль из сохранённой строки, отдельные колонки не нужны.
Минусы реальны. Footprint памяти у bcrypt — около 4 КБ, достаточно мало, чтобы топовая GPU гоняла тысячи bcrypt-ядер параллельно. И bcrypt тихо обрезает вход на 72 байтах. У 100-символьной парольной фразы та же безопасность, что и у её первых 72 байт. Максимальный cost — 31, но всё, что выше ~16, начинает бить по задержке логина на обычном железе.
scrypt: пионер memory-hard
scrypt опубликовал в 2009-м Колин Персиваль для сервиса бэкапов Tarsnap, стандартизирован как RFC 7914 в 2016-м. Он ввёл идею memory-hardness: алгоритм заполняет большой буфер псевдослучайными данными, затем читает из случайных позиций, заставляя любую реализацию реально выделить эту память.
scrypt берёт три параметра:
- N — стоимость по CPU/памяти (должна быть степенью двойки)
- r — размер блока в байтах (множитель на память и раунды смешивания)
- p — параллелизм (независимые вычисления, в основном для масштабирования CPU-времени без масштабирования памяти)
Использование памяти примерно 128 × N × r байт. С рекомендованными OWASP N=2^17, r=8 это 128 × 131072 × 8 = 134 217 728 байт, или 128 МБ на хеш.
scrypt также работает как функция деривации ключа, а не только как хеш пароля. Его можно встретить в кошельках криптовалют, полнодисковом шифровании и оригинальном proof-of-work Litecoin. Эта двойная роль удобна, когда нужны и хранение пароля, и деривация ключа в одной библиотеке.
Argon2 (id/i/d): победитель Password Hashing Competition
Password Hashing Competition шёл с 2013 по 2015 год, оценивая 24 кандидата по memory-hardness, устойчивости к побочным каналам и простоте реализации. Победил Argon2. Стандартизирован как RFC 9106 в 2021-м.
У Argon2 три варианта. Различие — в том, как адресуется память при смешивании:
- Argon2d использует data-dependent адреса памяти. Это даёт лучшую устойчивость к атакам GPU и ASIC, но утекает информацию через побочные каналы кеш-таймингов. Подходит для proof-of-work криптовалют, не для аутентификации.
- Argon2i использует data-independent адреса. Безопасен по побочным каналам, но чуть слабее против GPU-tradeoff атак.
- Argon2id — гибрид: первая половина первого прохода использует индексирование Argon2i (безопасно по побочным каналам), остальное — индексирование Argon2d (устойчивость к GPU). RFC 9106 явно рекомендует Argon2id для хеширования паролей, и так же делает OWASP.
Argon2 берёт три параметра:
- m — память в КБ
- t — стоимость по времени (число проходов по буферу памяти)
- p — параллелизм (число дорожек, обрабатываемых одновременно)
Хеш Argon2id использует формат строки PHC и выглядит так:
$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG
Как и в bcrypt, все параметры живут внутри строки, поэтому verify() не нужна таблица параметров.
Рекомендуемые параметры OWASP 2026
OWASP Password Storage Cheat Sheet — канонический ориентир. Числа ниже соответствуют его текущему гайдансу. Они консервативные, рассчитаны на типовой веб-сервер с бюджетом задержки 100–500 мс на логин, и вы всё равно должны провести бенчмарк на собственном железе перед отгрузкой.
Параметры Argon2id: первый выбор
Базовая рекомендация OWASP: m=19456 (19 МБ), t=2, p=1.
Если у вашего сервера больше запаса по RAM, работу можно перераспределить между памятью и временем. RFC 9106 публикует эквивалентные профили; OWASP допускает любой из этих:
| memoryCost (m) | timeCost (t) | parallelism (p) | RAM на хеш |
|---|---|---|---|
| 47104 | 1 | 1 | 46 МБ |
| 19456 | 2 | 1 | 19 МБ (базовый) |
| 12288 | 3 | 1 | 12 МБ |
| 9216 | 4 | 1 | 9 МБ |
| 7168 | 5 | 1 | 7 МБ |
Эвристика настройки. Сначала выберите m исходя из пикового бюджета RAM на одновременные логины. Если ожидается 100 одновременных логинов и есть 4 ГБ запаса — это 40 МБ на хеш. Затем увеличивайте t, пока один verify не займёт 100–500 мс на вашем продакшен-CPU. Оставьте p=1, если нет конкретной причины задействовать многоядерность (большинство веб-фреймворков и так дают каждому запросу свой поток).
Параметры scrypt: когда Argon2 недоступен
Рекомендация OWASP: N=2^17 (131072), r=8, p=1, что использует 128 МБ на хеш.
Если 128 МБ на одновременный логин — слишком много для вашего сервера, OWASP допускает более слабые профили:
| N | r | p | RAM на хеш |
|---|---|---|---|
| 2^17 | 8 | 1 | 128 МБ (предпочтительно) |
| 2^16 | 8 | 1 | 64 МБ |
| 2^15 | 8 | 1 | 32 МБ |
N должен быть степенью двойки. Увеличение r поднимает и память, и работу CPU пропорционально; увеличение p поднимает работу CPU без роста памяти на инстанс. Для хеширования паролей оставляйте r и p на дефолтах и крутите только N.
bcrypt: cost factor 10+ только для legacy
OWASP больше не рекомендует bcrypt для новых проектов, но он по-прежнему повсюду: Devise, Spring Security, ASP.NET Identity и бесчисленные самодельные системы аутентификации по умолчанию используют его.
Если вы заперты в bcrypt — правила такие:
- Минимальный cost factor: 10. Ниже 10 — одна GPU дочитает утёкшую базу за дни.
- Рекомендуется: 12–14, в зависимости от железа. На современном x86-сервере
cost=12берёт ~250 мс на хеш;cost=13— 500 мс. - Целевой диапазон 100–300 мс на verify на вашем продакшен-железе. Бенчмарк, не угадывание.
- Помните о лимите 72 байта на вход. Если пользователи могут выбирать парольные фразы, предварительно вычислите SHA-256 (см. FAQ).
Устойчивость bcrypt к GPU ограничена его 4-килобайтным footprint памяти. Никакой cost factor никогда не сравняется с memory-hardness Argon2id, поэтому выбирайте Argon2id, когда можете.
Для практического ориентира: на сервере EPYC 2024 года bcrypt(cost=12) выполняется примерно за 250 мс; на топовом ноутбуке — ближе к 350 мс. Если ваши числа отличаются от 100–500 мс на порядок, проверьте, действительно ли библиотека использует нативный bcrypt, а не падает в медленный JS-полифил (некоторые сборщики выбрасывают нативные зависимости в serverless-сборках).
PBKDF2: путь соответствия FIPS-140
PBKDF2 (RFC 8018) — алгоритм последней инстанции в гайдансе по безопасности. Он старше bcrypt, не memory-hard и сдаёт GPU-атакам быстрее любого из трёх выше. Но он — единственный примитив хеширования паролей, валидированный по FIPS-140, что важно для федерального правительства, медицины (HIPAA) и определённых финансовых деплоев.
Когда нужен PBKDF2, используйте:
- HMAC-SHA-256 как PRF (не SHA-1; не голый SHA-256 без HMAC)
- Минимум 600 000 итераций (базовый OWASP 2026)
- Минимум 16-байтная случайная соль на пароль
Если FIPS вас не касается — берите Argon2id. Фиксированный вывод и фиксированная память PBKDF2 значат, что каждый доллар, вложенный атакующим в кремний GPU, напрямую конвертируется в больше попыток в секунду.
NIST SP 800-63B называет PBKDF2-HMAC «approved» для хеширования паролей, но не доходит до того, чтобы рекомендовать его поверх memory-hard альтернатив. Читайте это так: NIST позволяет PBKDF2 потому, что иначе пришлось бы инвалидировать каждый legacy-деплой государства, а не потому, что это лучший выбор для greenfield-проекта.
Рамка решения: какой алгоритм выбрать?
Сравнительная таблица
| Параметр | bcrypt | scrypt | Argon2id | PBKDF2 |
|---|---|---|---|---|
| Memory-hard | Нет | Да | Да | Нет |
| Устойчивость к GPU | Средняя | Высокая | Очень высокая | Низкая |
| Устойчивость к побочным каналам | Средняя | Средняя | Высокая (id) | Средняя |
| Сложность параметров | 1 (cost) | 3 (N, r, p) | 3 (m, t, p) | 1 (iterations) |
| Зрелость библиотек | Отличная | Хорошая | Хорошая | Отличная |
| Лимит длины входа | 72 байта | Нет | Нет | Нет |
| Стандартизация | de facto | RFC 7914 | RFC 9106 | RFC 8018 |
| Статус OWASP 2026 | Только legacy | Альтернатива | Первый выбор | Только FIPS |
По умолчанию — Argon2id
Для нового проекта (типовое веб-приложение, современный стек Node/Python/Go/Rust/JVM, без FIPS-ограничений) — используйте Argon2id с m=19456, t=2, p=1. Получаете лучшую устойчивость к GPU и побочным каналам, доступную сегодня, формат с встроенными параметрами, переживающий апгрейды библиотек, и никакого 72-байтного лимита. Экосистема библиотек зрелая: argon2 в npm, argon2-cffi в PyPI, golang.org/x/crypto/argon2, крейт argon2 на crates.io — всё поддерживается и проходит бенчмарки.
Когда выбрать scrypt или bcrypt
Берите scrypt, когда Argon2 недоступен в вашем рантайме (по-настоящему редко в 2026-м; даже Cloudflare Workers и Deno его теперь поддерживают), или когда у вас уже есть scrypt-система в продакшене и стоимость миграции перевешивает прирост безопасности. scrypt — по-прежнему добротный алгоритм; ему просто не хватает шлифовки Argon2id по побочным каналам.
Берите bcrypt, когда вы поддерживаете legacy-систему, у вас жёсткое требование минимизации зависимостей (никакого нативного кода, никаких лишних пакетов) и 72-байтный лимит приемлем для вашей пользовательской базы. bcrypt отработал на интернет-масштабе два десятилетия; его сценарии отказа задокументированы.
Берите PBKDF2, когда так сказал регулятор. Это единственная причина. Если ваш аудитор примет Argon2id (а всё больше принимают для не-FIPS нагрузок) — берите Argon2id.
Распространённые ошибки
Большинство утечек хранения паролей за последнее десятилетие сводятся к горстке повторяющихся инженерных ошибок. Ни одна не экзотическая, и все ловятся ревью кода аутентификации со списком ниже перед глазами.
- Хеширование паролей голым SHA-256 или MD5. Самый крупный провал в хранении паролей. См. сравнение MD5 и SHA-256 о том, почему это неверно для паролей.
- Использование одной глобальной соли на всех пользователей. Соль обязана быть уникальной на запись. Argon2 и bcrypt сами генерируют её для вас; не переопределяйте это.
- Установка времени хеша ниже 50 мс. Вы обменяли безопасность на ускорение, которое пользователь не способен заметить. Цельтесь в 100–500 мс.
- Установка времени хеша выше 1 секунды. Вы создали DoS-вектор против собственного endpoint логина. Ограничьте сверху ~500 мс.
- Хеширование паролей на клиенте и отправка дайджеста на сервер. Хеш стал паролем. Любой, кто украл базу, может аутентифицироваться, не обращая хеш. Хешируйте на сервере, точка.
- Хранение параметров алгоритма в отдельной колонке. Формат строки PHC сам кладёт их в хеш. Используйте его.
- Логирование паролей или хешей в обработке ошибок. И то и другое — собственность пользователя, не вашего лог-агрегатора. Скрабьте их на слое разбора запроса до того, как они дойдут до любого логгера.
- Трактовка исключений
verify()как ошибок аутентификации. Библиотека, бросающая исключение на повреждённом сохранённом хеше, должна вынести ошибку наверх, а не молча провалиться в «неверный пароль». Различайте «неверный пароль» (вернуть 401) и «сохранённый хеш повреждён» (вернуть 500 и поднять on-call).
Реальная имплементация
Argon2id в Node.js
Пакет argon2 (нативные биндинги к референсной реализации) — канонический выбор в Node:
import argon2 from 'argon2';
// Хеширование при регистрации или смене пароля
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19456, // 19 МБ
timeCost: 2,
parallelism: 1,
});
// → '$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>'
// Verify при логине
const ok = await argon2.verify(hash, candidate);
if (!ok) throw new Error('Invalid credentials');
// Обнаружение устаревших параметров и re-hash при успешном логине
if (argon2.needsRehash(hash, { type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1 })) {
const upgraded = await argon2.hash(candidate, {
type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1,
});
await db.users.update({ id: user.id }, { password_hash: upgraded });
}
Шаг needsRehash — то, что делает долгосрочную миграцию безболезненной: каждый успешный логин становится возможностью обновить сохранённый хеш до текущих параметров, не беспокоя пользователя.
Тот же паттерн в Python с argon2-cffi:
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher(memory_cost=19456, time_cost=2, parallelism=1)
# Hash
stored = ph.hash(password)
# Verify
try:
ph.verify(stored, candidate)
except VerifyMismatchError:
raise ValueError('Invalid credentials')
# Re-hash при апгрейде параметров
if ph.check_needs_rehash(stored):
stored = ph.hash(candidate)
В Go с golang.org/x/crypto/argon2:
import (
"crypto/rand"
"golang.org/x/crypto/argon2"
)
func hashPassword(password string) ([]byte, []byte) {
salt := make([]byte, 16)
rand.Read(salt)
hash := argon2.IDKey([]byte(password), salt, 2, 19456, 1, 32)
return hash, salt
}
Стандартная библиотека Go не поставляет PHC-форматирование; если используете примитив argon2.IDKey напрямую, придётся самому кодировать параметры и соль рядом с хешом. Большинство Go-проектов используют обёртку вроде github.com/alexedwards/argon2id.
Rust с крейтом argon2 идиоматически похож:
use argon2::{Argon2, PasswordHasher, PasswordVerifier, password_hash::{SaltString, rand_core::OsRng}};
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default(); // Argon2id, m=19456, t=2, p=1 по умолчанию
let hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string();
// На verify
let parsed = argon2::password_hash::PasswordHash::new(&hash)?;
argon2.verify_password(candidate.as_bytes(), &parsed)?;
Во всех трёх рантаймах получаемая строка взаимозаменяема: хеш, созданный в Node, корректно проверяется в Python или Rust. Эта кросс-рантаймовая совместимость делает Argon2 более безопасной ставкой для полиглотных архитектур, чем алгоритм-специфичные обёртки.
Паттерн миграции bcrypt → Argon2id
Почти никогда не получится снести таблицу пользователей и начать заново. Реально работает паттерн «мягкого, ведомого логином» апгрейда:
Добавляем колонку для отслеживания алгоритма:
ALTER TABLE users ADD COLUMN password_algo VARCHAR(16) NOT NULL DEFAULT 'bcrypt';
При логине отправляем в нужный verifier:
async function verifyAndMaybeRehash(user, candidate) {
let ok;
if (user.password_algo === 'argon2id') {
ok = await argon2.verify(user.password_hash, candidate);
} else if (user.password_algo === 'bcrypt') {
ok = await bcrypt.compare(candidate, user.password_hash);
if (ok) {
// Успешный legacy verify → re-hash через Argon2id
const newHash = await argon2.hash(candidate, {
type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1,
});
await db.users.update({ id: user.id }, {
password_hash: newHash,
password_algo: 'argon2id',
});
}
}
return ok;
}
Установите окно sunset в 6–12 месяцев. На 9-м месяце пошлите письмо «ваш пароль хранится по устаревшему методу, пожалуйста, войдите для апгрейда». После 12-ти месяцев аккаунты, оставшиеся на bcrypt, требуют принудительного сброса пароля при следующем логине. Активные пользователи мигрируют прозрачно; неактивные получают одноразовое трение.
Тот же паттерн работает для миграции с scrypt или PBKDF2. Вся нужная вам state — колонка password_algo.
Pepper, лимиты длины и подводные камни кодировки
Несколько острых углов, которые кусают реальные деплои:
Pepper. Pepper — секрет уровня приложения, добавляемый к каждому паролю перед хешированием, хранящийся отдельно от базы (в KMS, env-переменной или Hashicorp Vault). Если ваша база утекла, а app-секрет — нет, утёкшие хеши неатакуемы без pepper. Применяйте через HMAC, не конкатенацией:
import { createHmac } from 'crypto';
const peppered = createHmac('sha256', process.env.PEPPER).update(password).digest();
const hash = await argon2.hash(peppered, { type: argon2.argon2id, /* ... */ });
Pepper ротируйте редко (это требует re-hash), но поддерживайте ротацию через версионирование: PEPPER_V2, с фолбэком на PEPPER_V1 при verify.
Лимит 72 байта в bcrypt. Если приходится использовать bcrypt и поддерживать пароли произвольной длины, предварительно вычислите SHA-256 и закодируйте base64 (избегая встроенных NUL-байтов, которые bcrypt также обрабатывает непоследовательно):
import { createHash } from 'crypto';
const prepped = createHash('sha256').update(password, 'utf8').digest('base64');
const hash = await bcrypt.hash(prepped, 12);
То же преобразование prepped обязано выполняться на verify. Задокументируйте это в коде аутентификации огромным комментарием, чтобы следующий, кто туда полезет, понял, что происходит.
Нормализация UTF-8. Строка "café" может быть закодирована как c-a-f-é (4 кодпоинта, NFC) или c-a-f-e + комбинирующий акут (5 кодпоинтов, NFD). Они выглядят одинаково, но дают разные хеши. Всегда нормализуйте к NFC перед хешированием:
const normalized = password.normalize('NFC');
Это кусает мобильные клавиатуры и copy-paste из PDF чаще, чем кажется.
Никогда не предхешируйте на клиенте. Клиентски вычисленный хеш, отправленный на сервер — это новый пароль. Любой, кто читает базу, аутентифицируется. Хешируйте на сервере, точка. JWT этого не меняют; см. как декодировать токен JWT о том, что JWT аутентифицируют, а что — нет.
Бенчмарк на продакшен-железе, не на ноутбуке. Ноутбук Intel 13-го поколения с Argon2id m=19456, t=2, p=1 заканчивает примерно за 35 мс. Те же параметры на инстансе t3.small EC2 — ближе к 180 мс; на Raspberry Pi 4 — свыше 600 мс. Возьмите железо, на котором реально пойдёт продакшен, замерьте 1000 verifies, настройте по медиане. Разброс задержки логина от cold-start serverless-контейнеров тоже стоит измерить; cold start Lambda может добавить 200–800 мс, не связанных с хешированием.
FAQ
В чём разница между хешированием паролей и шифрованием?
Хеширование — одностороннее: вычисляете отпечаток фиксированной длины, который нельзя обратить, чтобы восстановить вход. Шифрование двустороннее: с правильным ключом расшифровываете обратно к оригиналу. Для паролей обязательно применяется хеширование, не шифрование. Сервер не должен иметь возможности восстановить пароль ни одного пользователя, чтобы утечка базы не превратилась в утечку учётных данных.
Почему нельзя просто использовать SHA-256 для паролей?
SHA-256 построен ради скорости. Современная GPU вычисляет 22 миллиарда хешей SHA-256 в секунду, поэтому 8-символьный пароль из нижнего регистра в утёкшей базе падает за минуты. Хешам паролей нужны три свойства, которых SHA-256 лишён: намеренно медленное выполнение, соль на запись и memory-hardness. Принцип компромисса тот же, что объяснён в гайдансе «не используйте MD5 для безопасности» в нашем генераторе хешей, а как атакующие превращают слабые хеши в plaintext — читайте в энтропия пароля.
Безопасен ли bcrypt в 2026-м?
Сам bcrypt не сломан. Установка ключа на основе Blowfish остаётся криптографически добротной. Изменилась модель угроз: GPU и ASIC делают отсутствие memory-hardness в bcrypt значимой слабостью на фоне Argon2id. Позиция OWASP в 2026-м: bcrypt приемлем для legacy-систем с cost ≥ 10, но новые проекты выбирают Argon2id.
Argon2i, Argon2d, Argon2id — какой использовать?
Используйте Argon2id. RFC 9106 указывает его как рекомендованный вариант для хеширования паролей. Argon2i — data-independent (безопасен по побочным каналам, но слабее против GPU-tradeoff атак). Argon2d — data-dependent (силён против GPU, но уязвим к побочным каналам кеш-таймингов). Argon2id — гибрид, дающий оба свойства.
Как выбрать параметры Argon2id для приложения?
Стартуйте с базовой OWASP: m=19456, t=2, p=1. Затем бенчмарк на продакшен-CPU и подстройка:
- Решите бюджет RAM на логин (скажем, 50 МБ при пиковой одновременности).
- Установите
mв это значение или ниже. - Прогоните
argon2.hash()в цикле и замерьте wall-time. - Поднимайте
t, пока медиана не сядет между 100 и 500 мс.
Оставьте p=1, если не профилировали и не знаете, что многодорожечный параллелизм помогает вашему рантайму. Для высоконагруженных серверов аутентификации сдвиг в сторону большего t и меньшего m часто даёт лучший запас по RAM.
Что за лимит 72 байта в bcrypt и как обращаться с длинными парольными фразами?
bcrypt подаёт вход в установку ключа Blowfish, которая обрезает на 72 байтах. У 150-символьной парольной фразы та же безопасность, что и у её первых 72 байт; остальное игнорируется. Решение — предхешировать SHA-256 (32 байта) или SHA-512 (64 байта), закодировать base64, чтобы избежать NUL-байтов, и подать в bcrypt. У Argon2id и scrypt такого лимита нет; они принимают вход произвольной длины напрямую.
Можно ли мигрировать bcrypt на Argon2 без принудительного сброса паролей?
Да. Паттерн: храните оба алгоритма за колонкой password_algo, отправляйте verify в нужную библиотеку и при каждом успешном bcrypt-verify сразу же пере-хешируйте Argon2id и обновляйте запись. Активные пользователи мигрируют молча в обычной каденции своих логинов. Установите окно sunset 6–12 месяцев для неактивных аккаунтов, затем для записей, оставшихся на bcrypt, форсируйте сброс пароля. Тот же паттерн работает для любой миграции «алгоритм → алгоритм».
PBKDF2 — хороший выбор в 2026-м?
Только когда соответствие FIPS-140 диктует выбор: типично в федеральном правительстве, регулируемой медицине (HIPAA) и определённых финансовых системах. Используйте HMAC-SHA-256 как PRF минимум с 600 000 итераций. PBKDF2 не memory-hard, поэтому он сдаёт GPU-атакам быстрее Argon2id при эквивалентном бюджете задержки. Если FIPS не касается — выбирайте Argon2id и пропускайте лишнюю compliance-работу.
Ответ 2026 года по хешированию паролей короткий: по умолчанию — Argon2id с базовыми параметрами OWASP, фолбэк на scrypt при недоступности Argon2, bcrypt — только там, где требует legacy, PBKDF2 — для FIPS-связанных систем. Спарьте хеш с солью на запись (каждая современная библиотека делает это сама), app-уровневым pepper, хранящимся вне базы, и циклом re-hash, ведомым логином, который позволяет поднимать work factor по мере улучшения железа.
Сгенерируйте репрезентативный набор паролей генератором случайных паролей, забенчмаркайте verify-путь на продакшен-CPU и запишите параметры в файл констант, чтобы следующий инженер знал точно, что подкрутить в 2028-м. Полный контекст безопасности (TLS, управление сессиями, rate limiting, MFA) — в руководстве по основам веб-безопасности.