TurboCrypt переходит на кодировку Base84 ради безопасных имён файлов на Windows

TurboCrypt, утилита для шифрования файлов, изначально написанная для Unix-систем. Она шифрует не только содержимое, но и сами имена файлов, а получившийся шифротекст кодирует в виде текста, который можно сохранить как обычное имя файла. Для этого TurboCrypt использовал кодировку Base91: её алфавит устроен так, что результат остаётся валидным именем в Unix и macOS (в частности, его нормально принимает Finder, то есть кодировка учитывает не только файловую систему, но и то, как с именами работают конкретные приложения и библиотеки).

Когда у TurboCrypt попросили поддержку Windows, выяснилось, что несколько символов Base91-алфавита в именах файлов Windows запрещены. Поэтому TurboCrypt переходит на новую кодировку, Base84, которую автор материала считает нигде ранее не определённой и не используемой, хотя она идеально подходит для кодирования произвольных данных в переносимое, безопасное для всех основных файловых систем имя.

Алфавит Base84 построен так. Из 94 печатных ASCII-символов (не считая пробела) правила именования файлов в Windows запрещают девять: < > : " / \ | ? *. Остаётся 85 символов. Но имя, заканчивающееся точкой, ведёт себя ненадёжно в командной оболочке Windows и в обычных файловых API, поэтому точку тоже убрали, получилось 84 символа, которые можно использовать в любом месте компонента имени файла (эти ограничения задокументированы Microsoft). При этом ведущая точка Windows разрешена, например, .gitignore работает нормально, но отказ от точек в алфавите заодно избавляет от скрытых имён в Unix и от особых имён . и ...

Кодировщик zig-base84, реализация этой схемы. Он выдаёт данные группами по пять символов: пять оказалось оптимальным числом, потому что 84⁵ = 4 182 119 424, что всего на 2,6% меньше 2³². Это значит, что группа из пяти символов почти всегда (примерно в 95% случаев на случайных входных данных) вмещает полные 32 бита, а в остальных случаях, 31 бит. Кодировщик смотрит на очередные 31 бит: если их значение меньше 84⁵ − 2³¹, в группу помещается ещё один, 32-й бит, иначе группа ограничивается 31 битом, но в обоих случаях значение укладывается в пять base-84-символов. На случайных данных это даёт в среднем около 31,95 бита на группу, то есть 6,39 бита на символ, а результирующая строка получается примерно на 25,2% длиннее исходных двоичных данных, это почти уровень Base85. Худший случай, вход, целиком состоящий из байтов 0xFF: тогда каждая полная группа несёт только 31 бит, и расширение достигает примерно 29%.

Большинство файловых систем ограничивают длину имени 255 байтами, а поскольку алфавит Base84, чисто ASCII, это те же 255 символов. Пять делит 255 без остатка, поэтому даже имя максимальной длины состоит только из полных пятисимвольных групп, без потерь на неполную группу. В итоге Base84 гарантированно вмещает в имя файла 197 байт исходных данных, против 191 байта у Base64 без паддинга.

Для имён, которые не обязаны работать в Windows, автор указывает вариант из zig-base91: он заменяет слэш в стандартном алфавите Base91 на апостроф (стандартный Base91 всё ещё содержит / и, как и оба этих алфавита, символы, запрещённые в Windows). Этот Unix-only вариант даёт в среднем около 6,51 бита на символ на случайных данных и расширение примерно на 23%, то есть эффективнее по месту, чем кросс-платформенный Base84, но ценой несовместимости с Windows.

Отдельно решена проблема зарезервированных имён устройств Windows вроде CON, NUL и COM1 (регистр не имеет значения). Благодаря пятисимвольной упаковке кодировщик физически не может получить такие имена: трёхсимвольный вывод всегда заканчивается буквой от A до J, что исключает CON, PRN, AUX и NUL в любом регистре; четырёхсимвольный вывод всегда заканчивается заглавной буквой либо a, b или c и никогда не заканчивается цифрой, значит, COM1, COM9 и LPT1, LPT9 тоже невозможны. А поскольку в алфавите вовсе нет точек, зарезервированное имя с добавленным расширением получить тоже нельзя. Никакого паддинга или специальной обработки для защиты от таких имён не требуется.

Ключевые факты

  • TurboCrypt шифрует не только содержимое файлов, но и их имена, и кодирует результат так, чтобы получилось валидное имя файла; раньше для этого использовался Base91, безопасный для Unix и macOS, но не для Windows.
  • Из-за запроса на поддержку Windows разработчик перешёл на новый алфавит Base84: из 94 печатных ASCII-символов Windows запрещает 9, ещё один (точку) убрали из-за ненадёжной работы через оболочку и файловые API, осталось 84 символа.
  • Кодировщик zig-base84 упаковывает данные группами по 5 символов: 84⁵ = 4 182 119 424, что на 2,6% меньше 2³², и группа вмещает 32 бита примерно в 95% случаев (иначе 31 бит), в среднем 6,39 бита на символ.
  • Средний прирост размера при кодировании, около 25,2%, худший случай (вход из одних байтов 0xFF), около 29%; при лимите файловой системы в 255 байт Base84 вмещает 197 байт исходных данных против 191 у Base64 без паддинга.
  • Пятисимвольная упаковка гарантирует, что кодировщик не может случайно выдать зарезервированные Windows-имена вроде CON, NUL или COM1, ни паддинг, ни специальная обработка для этого не нужны; для имён, не обязанных работать в Windows, предложен более компактный (~23% расширения) вариант на основе Base91.

Почему это важно

Задача кажется мелкой, как назвать зашифрованный файл, но за ней стоит реальное ограничение: у разных операционных систем разные наборы запрещённых символов и зарезервированных имён, и универсального «безопасного везде» способа закодировать произвольные байты в текст имени файла до этого не существовало под готовым названием. Автор материала показывает, что задача решается точным подбором алфавита и битовой упаковки, а не компромиссом или обходным путём вроде дополнительного расширения файла.

Кому это важно

В первую очередь, разработчикам инструментов, которым нужно превращать произвольные бинарные данные (шифротекст, хеши, идентификаторы) в имена файлов, переносимые между Linux, macOS и Windows: утилитам шифрования вроде TurboCrypt, инструментам резервного копирования и синхронизации, менеджерам версий и кеширующим системам, которые хранят объекты под именами, производными от содержимого.

Как это применить

Готовая реализация, zig-base84; алфавит и правило упаковки по 5 символов описаны в материале настолько детально, что схему можно перенести на любой язык. Для сценариев, где Windows-совместимость не нужна, автор предлагает более компактный вариант на основе Base91 с заменой слэша на апостроф, он даёт меньшее расширение объёма (около 23% против 25,2% у Base84), но остаётся непригоден для Windows.

Можно ли доверять

Материал, техническая заметка со детальным математическим разбором: количество символов алфавита, вероятности заполнения битовых групп и проценты расширения выводятся из явных вычислений, а не заявляются на веру. Работа опубликована как объяснение решения для конкретного инструмента (TurboCrypt) и сопровождается ссылкой на реализацию (zig-base84). Обсуждение на Hacker News скромное, 21 балл и 7 комментариев, то есть независимой проверки сообществом пока немного.

Риски и подводные камни

Base84, узкоспециализированное решение именно для кодирования произвольных байтов в имена файлов, а не общий стандарт: статья описывает единственную известную реализацию (zig-base84), созданную для нужд одного инструмента. Использование латиницы для символов алфавита без сопровождающего русского термина в этом материале не требуется, сама тема сугубо техническая и рассчитана на разработчиков, знакомых с кодировками Base64/Base85/Base91.

«Файловая система может хранить произвольные имена файлов, это может быть верно для некоторых файловых систем, но не учитывает библиотеки и приложения поверх неё. Например, macOS Finder такое имя совсем не понравится.»

— автор материала