Перейти к содержимому

Пароль из 100 символов: почему bcrypt учитывает только 72 байта

Пользователь придумал пароль из 100 символов. Почему проверка учитывает только первые 72 байта

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

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

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

Как длинный пароль превращается в короткий

Предположим, пользователь задал строку длиной 100 символов. Если это латинские символы, каждый из них обычно занимает один байт в UTF-8. В таком случае bcrypt использует первые 72 знака, а оставшиеся 28 полностью игнорирует.

Например, эти две строки будут эквивалентны для bcrypt:

```text
A...A123456789
A...A987654321
```

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

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

Ограничение связано не с символами, а с байтами

Главная сложность возникает при использовании национальных алфавитов и emoji. bcrypt ограничивает вход не 72 символами, а 72 байтами.

В UTF-8 один латинский символ чаще всего занимает один байт. Кириллическая буква - два байта. Поэтому предел для русского текста достигается примерно на 36 символах. Тридцать седьмая буква может уже частично или полностью оказаться за границей учитываемой области.

Например, парольная фраза из нескольких русских слов может выглядеть достаточно длинной - 40 или 50 символов, - но bcrypt фактически обработает только её начало.

С emoji ситуация ещё сложнее. Одна кодовая точка обычно занимает четыре байта, поэтому в 72 байта помещается около 18 отдельных emoji. Составные изображения, включающие модификаторы, соединители или варианты оттенка кожи, могут занимать от 8 до 25 байт. В результате лимит иногда достигается уже после нескольких визуально заметных символов.

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

Откуда взялся предел в 72 байта

bcrypt построен на основе блочного шифра Blowfish. При подготовке ключа Blowfish формирует таблицу подключей, содержащую 18 элементов по 4 байта. В сумме это и даёт 72 байта.

Сам Blowfish способен работать с более длинными ключами, однако механизм формирования подключей использует циклическое обращение к ключевому материалу. Авторы bcrypt зафиксировали ограничение длины пароля на уровне 72 байт ещё в первоначальном описании алгоритма в 1999 году.

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

Опасна ли такая особенность для безопасности

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

Основная проблема находится в другой области - в несоответствии ожиданий и фактического поведения системы.

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

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

Три практические проблемы

Менеджеры паролей

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

Пока сайт, API и сервис авторизации работают на одной библиотеке, проблема может оставаться незаметной. Но при появлении второго компонента возникают сбои:

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

Воспроизвести такую ошибку бывает трудно, особенно если она проявляется только у паролей на кириллице или с emoji.

Миграция между библиотеками

Разные реализации bcrypt исторически обрабатывали превышение лимита неодинаково. Одна могла молча брать первые 72 байта, другая - выдавать исключение, третья - обрезать строку до 72 символов ещё до преобразования в UTF-8.

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

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

Неожиданные совпадения

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

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

Библиотеки постепенно переходят к явному отказу

Современные реализации начинают считать молчаливое обрезание плохой практикой. Например, Python-пакет bcrypt в ветке 4.x перестал хешировать и проверять пароли, превышающие допустимую длину.

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

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

Как правильно проектировать обработку паролей

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

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

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

Нужно также решить, разрешает ли система Unicode, пробелы, составные emoji и управляющие символы. Чем больше вариантов допускается, тем важнее одинаковая нормализация на клиенте и сервере.

Что проверить в действующей системе

Аудит стоит начать с инвентаризации всех мест, где пароль преобразуется или проверяется:

1. какая библиотека используется для bcrypt;
2. как обрабатываются строки длиннее 72 байт;
3. считается ли длина в символах или в байтах;
4. одинаково ли работают регистрация, вход и сброс пароля;
5. нет ли предварительного обрезания в веб-фреймворке;
6. совпадает ли поведение разных языков программирования;
7. сохраняются ли пароли менеджерами полностью;
8. что происходит при миграции старых хешей;
9. есть ли автоматические тесты для кириллицы и emoji;
10. выдаёт ли система понятную ошибку при превышении лимита.

Полезно подготовить набор тестовых строк одинаковой длины, но с разными кодировками: латиницей, кириллицей, emoji и комбинациями Unicode. В тестах необходимо отдельно проверить строки, у которых отличие появляется до 72-го байта, ровно на границе и после неё.

Как поступать при переходе на другой алгоритм

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

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

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

Ограничение bcrypt на 72 байта - не повод запрещать длинные пароли и не доказательство полной ненадёжности алгоритма. Это известная особенность, которую необходимо учитывать явно. Если приложение заранее проверяет размер строки в байтах, не обрезает ввод молча и одинаково работает во всех сервисах, неприятных сюрпризов при миграциях и авторизации удастся избежать.

Прокрутить вверх