Русский

Инструменты разработчика · Кодер и декодер Base64

RFC 4648 объяснил: стандарт, определяющий Base64, base32 и base16

· Фон

base64 кодирование

RFC 4648 семейства алфавитов: base64, base64url, base32, base32hex, base16
Оригинальная векторная иллюстрация ToolAcre

RFC 4648 — это короткий, читаемый документ, лежащий в основе каждой реализации Base64. В этом посте рассказывается о том, что он определяет, что намеренно оставляет открытым и почему реализации все еще различаются.

Две библиотеки, два ответа на одну и ту же строку — настоящая головоломка совместимости, которую решает только стандарт.

Две библиотеки JavaScript могут возвращать разные Base64 для одной и той же строки, каждая из которых утверждает корректность. RFC 4648 — это читаемый двенадцатистраничный документ, который должен разрешать такие разногласия, однако реализации по-прежнему различаются, поскольку RFC намеренно оставляет определенные решения на усмотрение приложений. В этой статье рассматривается, что определяет RFC 4648, что он намеренно делегирует вызывающим объектам и почему единоразовое чтение стандарта решает большинство реальных проблем совместимости. Инструмент кодирования и декодера Base64 включает RFC 4648 тестовые векторы, чтобы вы могли проверить реализацию на авторитетных примерах.

RFC 4648 заменил и объединил несколько более ранних документов: Base64 из MIME (RFC 2045), Base64 из Privacy-Enhanced Mail (RFC 1421), base32 из S/MIME (RFC 2630) и base16 из разных источников. Объединение было необходимо, поскольку MIME и PEM каждый имели свой собственный алфавит и правила, а перенос строк MIME конфликтовал с блоками столбцов PEM 64. RFC 4648 определяет пять семейств кодировок в одном месте: base64, base64url, base32, base32hex и base16, каждое из которых имеет свой собственный алфавит, правила заполнения и примеры тестовых векторов. Алфавит base64: A-Z, a-z, 0-9 плюс и косая черта в указанном порядке.

Что устанавливают векторы реализации и тестирования — стандартные и URL-безопасные алфавиты, заполнение и обработка пробелов.

Каждый символ представляет 6 bits; три входных байта (24 bits) соответствуют четырем выходным символам. Алфавит не является произвольным: он избегает символов, которые различаются между EBCDIC и ASCII, избегает управляющих символов, кавычек и обратной косой черты, которые необходимо было бы экранировать в строковых литералах C. Вариант base64url заменяет плюс на тире, а косую черту на подчеркивание, чтобы избежать зарезервированных символов в URL-адресах и именах файлов. Оба варианта одинаково действительны; RFC 4648 раздел 2 указывает base64, раздел 5 указывает base64url, и приложение должно указать, какой из них оно использует.

Заполнение символами равенства приводит к тому, что вывод становится кратным четырем символам. Если входные данные — 1 byte (8 bits), выходные данные — два символа плюс два знака равенства. Если входные данные — 2 bytes (16 bits), выходные данные — три символа плюс один знак равенства. Если входное значение кратно 3 bytes, заполнение не требуется. Некоторые приложения пропускают заполнение или допускают его отсутствие при декодировании; RFC 4648 раздел 3.2 определяет каноническое кодирование как всегда дополненное, но в разделе 3.3 отмечается, что декодеры могут принимать отсутствующие дополнения для совместимости.

Алфавиты, которые реализует этот инструмент, — стандартные Base64 и Base64url; другие базы остаются вне его сферы действия

Различие в заполнении объясняет, почему реализации расходятся во мнениях: строгий декодер отклоняет отсутствующие равные значения, а снисходительный принимает их. RFC 4648 явно говорит: заполняющий символ, равный, обычно кодируется в процентах при использовании в URL-адресах, поэтому, если вывод base64url используется непосредственно в параметре URL, заполнение не требуется и его следует опустить. Это предложение является одной из причин, по которой URL-безопасный режим и пропуск заполнения часто сочетаются, хотя они являются независимыми вариантами. Раздел 5 (base64url) не запрещает заполнение; он просто отмечает общепринятую практику.

Вызывающий абонент, выбирающий base64url, должен решить, требуется ли заполнение для принимающей системы. Символы, не являющиеся алфавитными, во входных данных обрабатываются разными декодерами по-разному. В разделе RFC 4648 3.1 указано: Реализации MUST отклоняют кодировку, если она содержит символы вне базового алфавита. Однако в разделе 3.3 отмечается, что MIME Base64 (RFC 2045) допускает разрывы строк для переноса символов 76, а декодеры для MIME должны пропускать пробелы. RFC различает строгое декодирование (отклонять все неалфавитные символы) и декодирование, совместимое с MIME (пропускать пробелы, отклонять другие символы).

Заполнение, неалфавитные символы и каноническая кодировка — разделы, которые объясняют большинство разногласий в декодерах.

Приложение должно выбрать, какому правилу следовать; стандарт определяет оба. Base32 использует A-Z и 2-7 (всего символов 32), кодируя пять входных байтов (40 bits) в восемь выходных символов. Base32hex заменяет алфавитные символы 0-9 и a-v, что полезно в контекстах, где предпочтительны строчные буквы.

Base16 является шестнадцатеричным: 0-9 и a-f. Base32 и base32hex имеют свои собственные правила заполнения в разделах 6 и 7, а RFC предоставляет отдельные тестовые векторы для каждого алфавита. Большинству разработчиков нужны только base64 и base64url; base32, base32hex и base16 включены в RFC для полноты и для таких приложений, как секреты TOTP (RFC 4226) и кодировка DNS.

Выбор приложения, видимый в этой реализации — перенос строк, строгое декодирование текста и обработка ошибок.

Векторы тестирования в RFC 4648 являются основной истиной для проверки реализации. Кодирование строк f, fo, foo, fob, fooba и foobar дает определенный вывод в формате Base64: Zg==, Zm8=, Zm9v, Zm9vYg==, Zm9vYmE= и Zm9vYmFy. Реализация, которая выдает разные выходные данные для этих строк, неверна. RFC предоставляет эквивалентные тестовые векторы для base32, base32hex и base16. Инструмент кодирования и декодера Base64 включает эти векторы, поэтому вы можете проверить его выходные данные на соответствие стандарту. Перенос строк — это проблема MIME, а не проблема base64.

RFC 2045 определяет строки из 76 символов; RFC 4648 раздел 3.1 отмечает это в контексте MIME, но не делает это требованием самого base64. Некоторые приложения переносят символы 64 (исходный стандарт PEM); другие вообще не заворачиваются. Строгий декодер RFC 4648 base64 работает только с алфавитом и заполнением. Декодер, совместимый с MIME, должен пропускать разрывы строк (CR, LF, CRLF). Приложение, использующее base64 за пределами MIME, не должно добавлять разрывы строк, если этого не требует принимающая система; RFC не определяет перенос строк как часть base64.

Рабочий пример: собственные тестовые векторы RFC — кодирование префиксов «foobar» и проверка их в браузере.

Обработка пробелов — еще одна особенность реализации. RFC 4648 сообщает, что строгие декодеры должны отклонять символы, не являющиеся алфавитными. MIME, завернутый в base64 (RFC 2045 base64), допускает пробелы для форматирования. Два стандарта согласны в том, какими должны быть выходные байты, но различаются в том, какие входные данные являются допустимыми. Большинство реализаций JavaScript выбирают совместимость MIME и пропускают пробелы; строгое правило редко используется в браузерах. Кодер и декодер Base64 принимает как содержащие пробелы (MIME), так и строгие входные данные, делая различие явным. Каноническое и прощающее декодирование — это последнее важное различие.

Каноническое декодирование следует за разделом RFC 4648 3.2: отклонять некорректное заполнение, отклонять отсутствующее заполнение, отклонять символы, не являющиеся алфавитными. Прощающее декодирование, используемое в веб-стандартах (спецификация HTML называет его прощающим base64), добавляет правила: игнорировать пробелы, принимать отсутствующие дополнения, разрешать тире и подчеркивание как эквиваленты с косой чертой даже в стандартном режиме base64. Функция atob() JavaScript прощает ошибки; строгий декодер RFC 4648 более строгий. Ни то, ни другое не является неправильным; они служат разным контекстам. Приложение, считывающее данные от пользователя или из сети, должно знать, какое правило ожидает другая сторона.

Что здесь не распространяется — сами документы MIME и PEM, а также API-интерфейсы для конкретного языка.

RFC оставляет приложению девять вариантов выбора: какой из пяти алфавитов, требовать или разрешать заполнение, требовать или разрешать пробелы, рассматривать ли подчеркивание тире как эквивалент косой черты, как сообщать об ошибках, как обрабатывать конец ввода, принимать ли отсутствующее заполнение, сколько выходных байтов выделить и как сигнализировать об ограничении размера. Этот выбор объясняет, почему две реализации RFC 4648 могут не соглашаться на один и тот же ввод. Прочтите RFC один раз; проверьте свою реализацию на соответствие ее тестовым векторам; укажите, какие параметры использует ваше приложение; тестируйте совместимость с реальным партнером, а не с предположениями.

Понимание RFC 4648 разрешает большинство споров по Base64, поскольку разногласия обычно возникают не по поводу самого RFC, а по поводу того, какие варианты выбрала каждая сторона. RFC достаточно краток, чтобы его можно было прочитать от начала до конца за час. Стандарт определяет алфавиты, предоставляет тестовые векторы и предупреждает, где реализации должны принять решение. Инструмент кодирования и декодера Base64 позволяет экспериментировать с тестовыми векторами и видеть стандартный алфавит в действии. В большинстве случаев повседневное использование base64 не требует глубоких знаний RFC; но при отладке несоответствий кодировки или интеграции с незнакомым API однократное чтение стандарта устраняет догадки.

Вывод: прочитайте стандарт один раз — как кодировщик и декодер Base64 позволяют быстро проверить тестовые векторы стандартного алфавита.

RFC 4648 — это объединение десятилетий специальной практики базового кодирования в одну удобочитаемую спецификацию. Он не определяет, когда использовать base64 (MIME, PEM, JWT, URI данных и т. д. каждый имеет свои собственные спецификации); он определяет, что такое base64. Определив пять семейств кодировок и отметив, какие параметры являются каноническими, RFC позволяет проверить правильность реализации. Авторитетные тестовые векторы являются отправной точкой: если ваша реализация кодирует foobar и выдает что-то отличное от Zm9vYmFy, RFC сообщает, что реализация неверна.

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