Загрузить файл
  1. base64hub
  2. Статьи о Base64
  3. Как работает Base64
Разбор

Как работает Base64: байт за байтом

Base64 переписывает любые байты 64 безопасными символами. На входе три байта, на выходе четыре символа, а всё остальное сводится к бухгалтерии.

Что такое 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 символа.

Для каждого блока:

  1. Взять 3 байта и записать их подряд как 24 бита.
  2. Разрезать эти 24 бита на четыре группы по 6 бит.
  3. Прочитать каждую группу как число от 0 до 63.
  4. Найти это число в алфавите и вывести соответствующий символ.

Алфавит задан фиксированной таблицей: 0 → A, 25 → Z, 26 → a, 51 → z, 52 → 0, 61 → 9, 62 → +, 63 → /. Полная таблица есть на странице алфавит Base64.

ДиапазонСимволыПример
0–25A–Z19 → T
26–51a–z46 → u
52–610–952 → 0
62+
63/

Декодирование выполняет те же шаги в обратном порядке: по каждому символу находим 6 бит, склеиваем четыре группы в 24 бита и делим их на 3 байта.

Пример: «Man» превращается в TWFu

Классический пример строится на ASCII-тексте Man. В ASCII у M код 77, у a 97, у n 110.

Man
Десятичное7797110
Двоичное010011010110000101101110

Склеиваем биты: 010011010110000101101110. Теперь режем их не по 8, а по 6:

6-битная группа010011010110000101101110
Значение1922546
СимволTWFu

Итак, 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-битная группа010011010110000100(нет)
Значение19224
СимволTWE=

Третья группа состоит из 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

Вопросы и ответы

Сжимает ли Base64 данные?

Нет. Он всегда увеличивает данные: каждые 3 байта ввода становятся 4 символами, это примерно на 33% больше. Точная формула приведена в статье о росте размера в Base64.

Почему он называется Base64?

Каждый символ результата принимает одно из 64 значений, то есть несёт 6 бит информации. Название отражает размер алфавита, так же как hex обозначает систему по основанию 16.

Можно ли закодировать в Base64 любые данные?

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

Бывают ли в Base64 пробелы или переносы строк?

В чистом Base64 по RFC 4648 их нет. Некоторые форматы разбивают его на строки, например почта MIME по 76 символов, а PEM-файлы по 64, и большинство декодеров такие пробельные символы пропускают.

Читайте также