Хронология
| Год | Документ | Что он добавил |
|---|---|---|
| 1987 | RFC 989 (PEM, часть I) | 64-символьная «печатная кодировка» с дополнением =, плюс * для незашифрованных фрагментов |
| 1988, 1989 | RFC 1040, RFC 1113 | Пересмотренные черновики PEM |
| 1992 | RFC 1341 (MIME) | Base64 как Content-Transfer-Encoding для почты |
| 1993 | RFC 1421 (PEM), RFC 1521 (MIME) | Строки PEM ровно по 64 символа, обновлённый MIME |
| 1996 | RFC 2045 (MIME) | Определение MIME, которое действует до сих пор: строки не длиннее 76 символов |
| 1997 | RFC 2152 (UTF-7) | Base64 внутри текстовой кодировки для Unicode |
| 2003 | RFC 3501 (IMAP4rev1) | Модифицированный Base64 с , для имён почтовых ящиков |
| 2003 | RFC 3548 | Первая самостоятельная спецификация: base64, base64url, base32, base16 |
| 2006 | RFC 4648 | Заменяет RFC 3548, добавляет base32hex и тестовые векторы |
| 2007 | RFC 4880 (OpenPGP) | Броня Radix-64 с контрольной суммой CRC-24 |
| 2015 | RFC 7468 | Строгие правила для файлов сертификатов и ключей в стиле PEM |
| 2015 | RFC 7515 (JWS) | Base64URL без дополнения для токенов |
| 2024 | RFC 9580 (OpenPGP) | Заменяет RFC 4880, объявляет контрольную сумму брони устаревшей |
PEM: с чего начался Base64
Алфавит, которым мы пользуемся сегодня, впервые появился в RFC 989 «Privacy Enhancement for Internet Electronic Mail», опубликованном в феврале 1987 года. Зашифрованная почта шла через шлюзы, которые пропускали только печатный текст, поэтому PEM нужен был способ записать двоичный шифротекст символами, которые переживут любую почтовую систему. Он брал по 6 бит и сопоставлял их символам A-Z, a-z, 0-9, + и /, а для дополнения использовал =. Таблица в RFC 989 совпадает с таблицей алфавита на этом сайте. Ещё там был зарезервирован * для пометки незашифрованных фрагментов, но эта возможность не прижилась.
PEM прошёл через RFC 1040 и RFC 1113 и окончательно оформился в RFC 1421 (февраль 1993 года), где была закреплена длина строки: каждая строка, кроме последней, содержит ровно 64 печатных символа, а последняя не длиннее 64. В качестве причины назывались печать и лимит SMTP в 1000 символов на строку. Как система защищённой почты PEM так и не взлетел, а вот его формат файлов остался. Каждый блок -----BEGIN CERTIFICATE----- происходит от него, и RFC 7468 (2015) теперь прямо прописывает правила: генераторы обязаны переносить строки ровно через 64 символа, парсеры могут принимать и другую ширину. Такие файлы удобно разбирать в декодере сертификатов.
MIME: Base64 для каждого почтового вложения
MIME (RFC 1341 в 1992 году, RFC 1521 в 1993-м, RFC 2045 в 1996-м) понадобился способ вкладывать в письма картинки и документы, и он позаимствовал кодировку у PEM. RFC 2045 до сих пор остаётся основным документом для электронной почты и отличается от PEM в двух местах:
- Закодированные строки не должны быть длиннее 76 символов и разделяются CRLF.
- Любые символы вне алфавита base64 в закодированных данных должны игнорироваться. Декодеры пропускают переносы строк, пробелы и всё остальное, что не входит в алфавит.
Именно из-за второго правила так много декодеров молча выбрасывают недопустимые символы. Вывод в стиле MIME делает кодировщик MIME Base64, а как устроены вложения, рассказано в статье Base64 в электронной почте.
RFC 3548 и RFC 4648: один документ для всех
До 2003 года каждый протокол, использовавший Base64, ссылался на MIME или PEM и вместе с ними получал их правила о длине строк и снисходительном декодировании, нужны они были ему или нет. RFC 3548 (июль 2003 года) определил base64, base64url, base32 и base16 как самостоятельные кодировки. RFC 4648 (октябрь 2006 года) заменил его с небольшими изменениями: добавил base32hex, который сохраняет порядок сортировки закодированных данных, и набор тестовых векторов:
BASE64("") = ""
BASE64("f") = "Zg=="
BASE64("fo") = "Zm8="
BASE64("foo") = "Zm9v"
BASE64("foobar") = "Zm9vYmFy"
Правила RFC 4648 строже, чем у MIME, если ссылающаяся на него спецификация не говорит иного:
- Переводы строк: реализации не должны их добавлять (раздел 3.1).
- Дополнение: реализации должны добавлять
=(раздел 3.2). - Символы вне алфавита: декодеры должны отклонять такие данные (раздел 3.3).
- Каноническая форма: кодеры должны обнулять неиспользуемые биты, а декодеры могут отклонять ввод, где они не обнулены (раздел 3.5). Без этого правила и
SGk/Pg==, иSGk/Ph==декодируются вHi?>.
Оговорка «если ссылающаяся на этот документ спецификация явно не указывает иное» значит очень многое. Благодаря ей JWS может отказаться от дополнения, а MIME сохранить строки по 76 символов, и при этом оба используют «Base64 по RFC 4648». Две другие кодировки из того же RFC реализуют инструменты Base32 и Base16.
Варианты из других стандартов
JWS и JWT: Base64URL без дополнения
RFC 7515 (май 2015 года) определяет BASE64URL(x) как алфавит из раздела 5, у которого отброшены все завершающие символы =, без переносов строк и пробелов. Так работают JWT, JWE, JWK и большая часть семейства JOSE. Подробности есть в статье Base64 в JWT.
IMAP: modified UTF-7 с запятой
Имена почтовых ящиков в IMAP записываются 7-битными строками, поэтому RFC 3501 (раздел 5.1.3) кодирует имена папок с не-ASCII символами в «modified UTF-7». Последовательности не-ASCII символов переводятся в UTF-16, кодируются в Base64 без дополнения, с , вместо /, и заключаются между & и -. Пример из самого RFC &U,BTFw- кодирует китайское название 台北, а немецкая папка Entwürfe превращается в Entw&APw-rfe. Обычный UTF-7 (RFC 2152) записывает то же имя как Entw+APw-rfe.
OpenPGP: Radix-64 с CRC-24
ASCII-броня OpenPGP устроена как MIME Base64 плюс строка с контрольной суммой. RFC 4880 определяет CRC-24 (порождающий многочлен 0x864CFB, начальное значение 0xB704CE) над двоичными данными; сумма кодируется в Base64 и предваряется символом =. Вот что GnuPG выдаёт для 12 байт Hello world!:
-----BEGIN PGP ARMORED FILE-----
Comment: Use "gpg --dearmor" for unpacking
SGVsbG8gd29ybGQh
=s4Gu
-----END PGP ARMORED FILE-----
RFC 4880 допускает строки до 76 символов, GnuPG пишет по 64. RFC 9580 (июль 2024 года) сохраняет формат, но говорит, что строку CRC-24 генерировать не следует (SHOULD NOT) и что реализации не должны отклонять блок, если она отсутствует или неверна.
Таблица вариантов Base64
| Вариант | Индексы 62 и 63 | Дополнение | Длина строки | Где определён |
|---|---|---|---|---|
| Стандартный Base64 | + / | Обязательно | Без ограничений | RFC 4648, раздел 4 |
| Base64URL | - _ | По усмотрению ссылающейся спецификации | Без ограничений | RFC 4648, раздел 5 |
| JWS / JWT | - _ | Никогда | Без ограничений | RFC 7515 |
| MIME | + / | Обязательно | Не больше 76, CRLF | RFC 2045 |
| PEM | + / | Обязательно | Ровно 64 | RFC 1421, RFC 7468 |
| Броня OpenPGP | + / | Обязательно | Не больше 76 (GnuPG: 64) | RFC 4880, RFC 9580 |
| UTF-7 | + / | Нет | Неприменимо | RFC 2152 |
| Modified UTF-7 в IMAP | + , | Нет | Неприменимо | RFC 3501 |
| bcrypt | Алфавит ./A-Za-z0-9 | Нет | Неприменимо | Де-факто (OpenBSD) |
| Хеши crypt(3) | Алфавит ./0-9A-Za-z | Нет | Неприменимо | Де-факто (Unix) |
Последние две строки переставляют весь алфавит, а не только два последних символа. О них рассказано на странице алфавита. Многие другие форматы ссылаются на одну из строк выше, ничего не меняя: HTTP Basic-аутентификация (RFC 7617), data URI (RFC 2397), base64Binary из XML Schema и хеши Subresource Integrity используют стандартный Base64 с дополнением.
Вопросы и ответы
Какой RFC описывает Base64?
Основным справочным документом служит RFC 4648 (октябрь 2006 года). В нём определены base64, base64url, base32, base32hex и base16. Версия MIME описана в RFC 2045, исходная версия PEM в RFC 1421.
Почему строки в MIME по 76 символов, а в PEM по 64?
Оба ограничения держат строки заметно короче лимита SMTP в 1000 символов. PEM зафиксировал 64 в RFC 1421, MIME в своей спецификации выбрал «не больше 76». RFC 4648 вообще запрещает кодерам вставлять переносы строк, если этого не требует ссылающийся на него стандарт.
Есть ли у Base64URL официальный стандарт?
Да. Это раздел 5 RFC 4648, «Base 64 Encoding with URL and Filename Safe Alphabet», а впервые он появился в RFC 3548. JWS (RFC 7515) использует его без дополнения.
Что за строка =XXXX в конце PGP-блока?
Это контрольная сумма CRC-24 декодированных данных, тоже записанная в Base64. RFC 9580 (2024) рекомендует новым реализациям её не выводить и запрещает отклонять блок из-за неё.