Ограничение длины входной строки: 72 байта и его последствия

Как устроено ограничение в bcrypt

Алгоритм bcrypt был разработан в эпоху, когда основным ориентиром были криптографические библиотеки на C и системное хранение паролей в Unix. Одной из ключевых технических особенностей реализации стало жесткое ограничение на размер входных данных: все символы после первых 72 байт игнорируются.

В bcrypt.js, как JavaScript-портировании оригинального алгоритма, это ограничение сохранено полностью, поскольку оно заложено не на уровне библиотеки, а на уровне самой криптографической схемы.

Важно понимать:

bcrypt не работает с символами, он работает с байтами

Это означает, что строка в JavaScript сначала кодируется (обычно в UTF-8), и уже затем ограничивается первыми 72 байтами.


Почему именно 72 байта

Ограничение связано с исторической реализацией bcrypt на базе алгоритма Blowfish. Внутренний ключевой материал имеет фиксированный размер, и при расширении ключа используется только ограниченный буфер.

Фактически:

  • bcrypt использует 18-словный массив P-array и S-boxes
  • ключ расширяется через специальную функцию key setup
  • входной пароль обрезается до 72 байт, чтобы вписаться в структуру расширения ключа Blowfish

Это не искусственное ограничение JavaScript-реализации, а фундаментальная часть криптографической конструкции.


Как работает усечение строки

При обработке пароля происходит следующая последовательность:

  1. Строка преобразуется в UTF-8 байты
  2. Полученный байтовый массив обрезается до первых 72 байт
  3. Остальные байты полностью игнорируются
  4. На основе усечённого значения вычисляется хэш

Пример:

Пароль: "password123" + очень длинная строка

Если длинная строка выходит за пределы 72 байт, она никак не влияет на результат хэширования.


Критическое последствие: потеря части пароля

Самое важное последствие ограничения — потеря энтропии при длинных паролях.

Пример проблемы

Рассмотрим два пароля:

A: "correcthorsebatterystaple"
B: "correcthorsebatterystaple + 1000 символов"

Если первые 72 байта совпадают, то:

  • bcrypt выдаст одинаковый хэш
  • различия после 72 байт полностью игнорируются

Это создаёт классическую проблему:

длинный пароль не всегда означает более безопасный пароль


Влияние кодировки UTF-8

В JavaScript строки используют UTF-16, но bcrypt.js при обработке переводит их в UTF-8. Это приводит к дополнительным нюансам:

  • ASCII символы: 1 байт
  • кириллица: обычно 2 байта
  • emoji: 4 байта

Пример:

"а" (кириллица) → 2 байта
"?" → 4 байта

Следствие:

72 символа ≠ 72 байта

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


Практический эффект для длинных паролей

1. Потеря различимости паролей

Если пользователь вводит пароль длиной более 72 байт:

  • bcrypt учитывает только первые 72 байта
  • остальная часть игнорируется
  • разные пароли могут стать эквивалентными на уровне хэша

2. Уязвимость к атакующим стратегиям

Злоумышленник может:

  • подбирать совпадение только по первым 72 байтам
  • игнорировать остаток строки
  • сокращать пространство перебора

Это не делает bcrypt “сломленным”, но снижает эффективность длинных паролей как средства защиты.


3. Ложное чувство безопасности

Системы часто принимают:

  • “чем длиннее пароль, тем лучше”

Но в контексте bcrypt:

  • после 72 байт увеличение длины не повышает стойкость
  • дополнительная строка не участвует в хэшировании

Типичные ошибки разработчиков

Ошибка 1: попытка хранить очень длинные пароли

Некоторые системы разрешают пароли:

  • 128 символов
  • 256 символов
  • даже больше

Но при использовании bcrypt.js:

всё после 72 байт просто исчезает из криптографического расчёта


Ошибка 2: двойное хэширование до bcrypt

Иногда пытаются обойти ограничение:

const hashed = bcrypt.hashSync(sha256(password), salt);

Это приводит к:

  • потере смысла bcrypt как адаптивного хэша
  • снижению устойчивости к GPU-атакам (в некоторых сценариях)

Ошибка 3: отсутствие контроля длины на уровне формы

Если система не ограничивает длину входа:

  • пользователь может вводить 10 000 символов
  • система думает, что это повышает безопасность
  • фактически учитываются только первые 72 байта

Как это проявляется в реальных системах

Сценарий 1: разные пароли — одинаковый хэш

Пароль A: "securepassword123AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
Пароль B: "securepassword123BBBBBBBBBBBBBBBBBBBBBBBBBBBB"

Если различия начинаются после 72 байт:

  • хэши будут одинаковыми
  • система не сможет различить пользователей

Сценарий 2: миграция с других алгоритмов

При переходе с:

  • PBKDF2
  • Argon2
  • scrypt

на bcrypt.js может возникнуть неожиданное поведение:

  • длинные пароли перестают быть уникальными
  • часть базы становится логически коллизионной

Рекомендации архитектурного уровня

1. Ограничение длины входа

Практическая мера:

  • ограничивать пароль до 72 байт
  • учитывать UTF-8 байтовую длину, а не символы

2. Явная нормализация строки

Перед хэшированием:

  • привести строку к UTF-8
  • проверять байтовую длину
  • валидировать до bcrypt

3. Осознанное принятие лимита bcrypt

Важно учитывать:

  • bcrypt не предназначен для длинных произвольных строк
  • его модель безопасности основана на фиксированном входе

Сравнение с современными алгоритмами

В отличие от bcrypt:

  • Argon2 не имеет столь жёсткого лимита
  • scrypt масштабируется по памяти и длине
  • PBKDF2 зависит от выбранной функции хэширования

bcrypt.js сохраняет ограничение:

72 байта — это фундаментальная константа алгоритма


Итоговые последствия ограничения

Ключевые эффекты:

  • обрезка пароля до 72 байт
  • потеря информации после лимита
  • отсутствие влияния сверхдлинных паролей на безопасность
  • потенциальные коллизии при совпадении префиксов
  • необходимость контроля длины на стороне приложения