Что такое Base64 и зачем он нужен
Base64 позволяет записать произвольные двоичные данные только 64 печатными символами ASCII: A–Z, a–z, 0–9, + и /, плюс = для дополнения. Это кодирование, а значит, обратить его может любой и без ключа. Ничего он не скрывает (подробнее об этом в статье кодирование и шифрование).
Смысл в выживании. В PNG-файле есть байты вроде 0x00, 0x1A и 0x89, которые текстовый канал может вырезать, перекодировать или принять за управляющий код. В Base64 тот же файл превращается в iVBORw0KGgo..., и это без изменений проходит через почту, JSON, XML, URL (с URL-safe вариантом), терминалы и копирование-вставку.
Актуальное определение даёт RFC 4648. Более старые спецификации для почты MIME (RFC 2045) и сертификатов PEM описывают тот же алфавит с дополнительными правилами о длине строк. Сводка вариантов собрана на странице стандарты Base64.
Откуда он взялся
Ранняя электронная почта была рассчитана на 7-битный текст ASCII. Почтовые релеи того времени могли срезать старший бит каждого байта, переносить длинные строки и переписывать окончания строк. Для письма это безобидно, для ZIP-файла смертельно. MIME нужен был способ протащить через эту трубу любое вложение, и нужен был алфавит, который переживёт каждую систему по дороге.
Base64 выбирает свои 64 символа именно поэтому. Буквы и цифры одинаковы в ASCII и почти во всех старых кодировках, + и / почти так же безопасны, и среди них нет ни управляющих символов, ни пробельных. Кроме того, 64 оказывается наибольшей степенью двойки, которую можно набрать из этого безопасного множества, а степень двойки упрощает работу с битами. Платить приходится размером: 6 полезных бит на 8-битный символ означают, что результат составляет 4/3 от ввода плюс дополнение.
В современных почтовых серверах проблема 8 бит почти исчезла, но то же ограничение возникает везде, где двоичные данные должны жить внутри текстового формата. В JSON нет двоичного типа, атрибуты HTML, CSS и заголовки HTTP текстовые, поэтому Base64 по-прежнему стандартный способ положить в них байты.
Алгоритм: 3 байта на входе, 4 символа на выходе
Base64 работает блоками по 24 бита. Это наименьшее число, которое делится и на 8 (байт), и на 6 (один символ Base64), поэтому 3 байта точно ложатся на 4 символа.
Для каждого блока:
- Взять 3 байта и записать их подряд как 24 бита.
- Разрезать эти 24 бита на четыре группы по 6 бит.
- Прочитать каждую группу как число от 0 до 63.
- Найти это число в алфавите и вывести соответствующий символ.
Алфавит задан фиксированной таблицей: 0 → A, 25 → Z, 26 → a, 51 → z, 52 → 0, 61 → 9, 62 → +, 63 → /. Полная таблица есть на странице алфавит Base64.
| Диапазон | Символы | Пример |
|---|---|---|
| 0–25 | A–Z | 19 → T |
| 26–51 | a–z | 46 → u |
| 52–61 | 0–9 | 52 → 0 |
| 62 | + | |
| 63 | / |
Декодирование выполняет те же шаги в обратном порядке: по каждому символу находим 6 бит, склеиваем четыре группы в 24 бита и делим их на 3 байта.
Пример: «Man» превращается в TWFu
Классический пример строится на ASCII-тексте Man. В ASCII у M код 77, у a 97, у n 110.
| M | a | n | |
|---|---|---|---|
| Десятичное | 77 | 97 | 110 |
| Двоичное | 01001101 | 01100001 | 01101110 |
Склеиваем биты: 010011010110000101101110. Теперь режем их не по 8, а по 6:
| 6-битная группа | 010011 | 010110 | 000101 | 101110 |
|---|---|---|---|---|
| Значение | 19 | 22 | 5 | 46 |
| Символ | T | W | F | u |
Итак, Man кодируется в TWFu. Обратите внимание: ни один символ результата не совпадает с буквой ввода. W собран из последних 4 бит M и первых 2 бит a. Поэтому изменение одного байта ввода меняет два символа результата, и поэтому Base64 не прочитать на глаз, даже если внутри обычный английский текст.
Проверить можно в любой оболочке:
echo -n Man | base64
# TWFu
-n важен. Без него echo добавляет байт перевода строки, и Man\n кодируется в TWFuCg==.
Когда длина не кратна 3: дополнение
Длина реальных данных редко делится на 3. Тогда в последнем блоке остаётся 1 или 2 байта, и Base64 дополняет его нулевыми битами и знаками =.
Осталось два байта, «Ma» → TWE=. Два байта дают 16 бит. Добавляем два нулевых бита, чтобы получить 18, то есть три 6-битные группы:
| 6-битная группа | 010011 | 010110 | 000100 | (нет) |
|---|---|---|---|---|
| Значение | 19 | 22 | 4 | |
| Символ | T | W | E | = |
Третья группа состоит из 0001 от a и двух добавленных нулей, то есть равна 4, а это E. Один = заменяет отсутствующий четвёртый символ.
Остался один байт, «M» → TQ==. Один байт даёт 8 бит. Добавляем четыре нулевых бита до 12, получаем две группы: 010011 (19, T) и 010000 (16, Q). Блок заполняют два знака =.
Так что количество = говорит, сколько байтов было в последнем блоке: без знаков равенства их было 3, с одним 2, с двумя 1. Третьего = не бывает никогда, потому что блока с нулём оставшихся байтов не существует. Когда = можно отбросить и как его вернуть, рассказано в статье о дополнении.
import base64
for s in [b"Man", b"Ma", b"M"]:
print(s, base64.b64encode(s))
# b'Man' b'TWFu'
# b'Ma' b'TWE='
# b'M' b'TQ=='
Сначала текст нужно превратить в байты
Base64 кодирует байты. Текст становится байтами только после выбора кодировки символов, и от этого выбора зависит результат. Слово café в UTF-8 занимает 5 байт (é занимает два), поэтому кодируется в Y2Fmw6k=. В Latin-1 это 4 байта и Y2Fm6Q==.
Отсюда известная ловушка JavaScript. Браузерная функция btoa() считает каждый символ строки одним байтом, то есть ведёт себя как Latin-1:
btoa("café"); // "Y2Fm6Q==" (Latin-1, not UTF-8)
btoa("café ✓"); // throws InvalidCharacterError
Чтобы получить Base64 от UTF-8 в JavaScript, сначала переведите строку в байты через TextEncoder или используйте Buffer в Node.js. Рабочие варианты собраны на странице про JavaScript, а более новый встроенный метод разобран в статье Uint8Array.toBase64(). Кодировщик Base64 на этом сайте по умолчанию использует UTF-8 и позволяет выбрать другую кодировку.
Где встречается Base64
- Почтовые вложения. Каждое вложение в исходном файле
.emlзаписано блоком Base64 со строками по 76 символов. См. Base64 в электронной почте. - Data URI.
data:image/png;base64,iVBORw0KGgo...встраивает картинку прямо в HTML или CSS. Хорошая ли это идея, разобрано в статье производительность data URI. - HTTP Basic Auth. Заголовок
Authorization: Basic dXNlcjpwYXNzсодержит простоuser:passв Base64. - JWT. Три части JSON Web Token, разделённые точками, записаны в Base64URL. См. Base64 в JWT.
- PEM-файлы. Сертификаты и ключи между строками
-----BEGIN ...-----хранятся как Base64 от двоичных данных DER. - API и конфиги. Секреты Kubernetes, облачные метаданные, данные вебхуков и многие JSON API передают файлы и двоичные блобы строками Base64.
Со временем начинаешь узнавать блобы по первым символам: iVBORw0KGgo (PNG), JVBERi0 (PDF), /9j/ (JPEG), eyJ (JSON, начинающийся с {"). Больше примеров собрано на странице типичные префиксы Base64. Чтобы увидеть, что внутри блоба, воспользуйтесь инструментом Base64 в файл: он определит формат и покажет превью в браузере.
Вставьте Base64 и посмотрите текст или байты внутри. Всё работает локально в браузере.
Вопросы и ответы
Сжимает ли Base64 данные?
Нет. Он всегда увеличивает данные: каждые 3 байта ввода становятся 4 символами, это примерно на 33% больше. Точная формула приведена в статье о росте размера в Base64.
Почему он называется Base64?
Каждый символ результата принимает одно из 64 значений, то есть несёт 6 бит информации. Название отражает размер алфавита, так же как hex обозначает систему по основанию 16.
Можно ли закодировать в Base64 любые данные?
Да. Алгоритм работает с байтами и не интересуется их смыслом, поэтому текст, картинки, PDF, ключи и сжатые архивы кодируются одинаково. Текст сначала нужно превратить в байты с помощью кодировки символов, обычно UTF-8.
Бывают ли в Base64 пробелы или переносы строк?
В чистом Base64 по RFC 4648 их нет. Некоторые форматы разбивают его на строки, например почта MIME по 76 символов, а PEM-файлы по 64, и большинство декодеров такие пробельные символы пропускают.