Суть Читай реальные auth-сниппеты — параметры Argon2id, наивное хранение SHA-256, timing-unsafe сравнение и отсутствующий rehash-on-login — и выбирай фикс, который сделает senior первым.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги в паролях прячутся в коде, который выглядит нормально и проходит happy-path тест. Прочитай каждый сниппет, найди дефект, который вскрыла бы утечка или нагрузочный тест, и выбери фикс, за которым senior тянется первым.
Цель
Отработай цикл ревью: прочитать auth-путь, найти криптографический или тайминговый дефект и выбрать максимально рычажную правку до отправки в production.
Что здесь не так и каков один максимально рычажный фикс?
Heads-up Salt убивает rainbow tables, но скорость SHA-256 не трогает — утёкшая колонка всё равно ломается за часы. Надо менять алгоритм на медленный KDF, а не просто прикручивать salt.
Heads-up SHA-512 тоже быстрый универсальный хеш; более длинный вывод не замедляет перебор. Фикс — намеренно медленный memory-hard KDF, а не больший быстрый хеш.
Heads-up Кодировка косметична и ничего не меняет в взламываемости. Проблема — выбор алгоритма и отсутствующий salt.
Heads-up argon2id — ровно верный вариант: он смешивает data-independent и data-dependent проходы ради устойчивости и к GPU, и к side-channel. Баг — заниженные memory и time cost, а не вариант.
Heads-up p=1 соответствует рекомендации OWASP; parallelism — ручка пропускной способности, а не рычаг безопасности. Реальный дефект — memory cost в 512 KiB.
Heads-up 512 KiB — это примерно 1/38 от минимума OWASP в 19 MiB, поэтому GPU снова гоняет много попыток параллельно. Эти параметры опасно слабые, а не консервативные.
Сниппет 3 — сравнение
function verifyResetToken(provided, stored) { // оба — hex-кодированные HMAC-дайджесты if (provided === stored) { return true; } return false;}
Викторина
Completed
Здесь два секретных дайджеста сравниваются через ===. Почему это проблема и каков фикс?
Heads-up Строковый === в V8 замыкается на первом несовпавшем символе, поэтому его длительность зависит от числа совпавших ведущих символов — ровно та утечка. Используй constant-time сравнение.
Heads-up Возврат boolean — корректный дизайн API. Дефект — тайминговая вариативность ===, а не тип возврата.
Heads-up Перехеширование не делает финальное сравнение constant-time, а сравнение перехешей через === имеет ту же утечку. Используй timing-safe примитив равенства на байтах.
Сниппет 4 — вход без миграции
async function login(email, password) { const user = await db.users.findByEmail(email); if (!user) return null; const ok = await argon2.verify(user.passwordHash, password); if (!ok) return null; return issueSession(user);}
Викторина
Completed
Verify верный и constant-time. Что этот вход упускает, что важно по мере старения параметров?
Heads-up Железо ускоряется, поэтому параметры 2020 сегодня слабы. Вход — единственный момент, когда ты держишь plaintext и можешь прозрачно перехешировать, ровно тогда миграция и должна происходить.
Heads-up Перехеширование при каждом входе тратит CPU и бесполезно, когда параметры уже совпадают. Перехешируй только когда needsRehash сообщает, что хранимые параметры устарели.
Heads-up Принудительный сброс не нужен и враждебен пользователю. Plaintext уже есть при успешном входе, поэтому можно перехешировать молча без сброса.
Итог
Это дефекты, которые находишь в реальных auth-ревью: быстрый хеш без salt должен стать медленным, memory-hard KDF; Argon2id с заниженным memory cost выбрасывает свойство, душащее GPU, поэтому соблюдай OWASP m=19 MiB/t=2/p=1; сравнения секретов должны быть constant-time (crypto.timingSafeEqual или собственный verify KDF), а не ===; а корректный verify всё равно нуждается в rehash-on-login через needsRehash, чтобы держать work factor актуальным без принудительных сбросов. Диагностируй алгоритм, параметры, тайминг и миграцию — именно в этом порядке.