Кодирование и декодирование Base64

В обе стороны, с корректным Unicode и URL-безопасным алфавитом, когда он нужен.

Для чего он нужен

Base64 решает одну задачу: у вас есть произвольные байты, а канал переносит только печатный текст. Заголовки писем, строковые поля JSON, URL, атрибуты HTML и немалая часть старой инфраструктуры спотыкаются о сырые двоичные данные. Base64 отображает каждые три байта на четыре символа из алфавита в 64 знака, который всё это переживает.

В этом вся его задача. Это не сжатие — результат на треть длиннее. Это не шифрование — ключа нет. Это транспортное кодирование, и всякое свойство, которое ему ошибочно приписывают, растёт из подмены «я не могу это прочесть» на «это защищено».

Где вы с ним встретитесь

  • JWT. Все три сегмента, в URL-безопасном алфавите и без дополнения.
  • Базовая аутентификация HTTP. Authorization: Basic — это просто user:password, закодированные в Base64; ровно поэтому Basic-аутентификация поверх обычного HTTP равносильна отправке пароля открытым текстом.
  • Data URI. data:image/png;base64,... встраивает файл прямо в HTML или CSS.
  • Сертификаты и ключи PEM. Строки между -----BEGIN----- и -----END----- — это DER, закодированный в Base64.
  • Вложения в почте. SMTP — текстовый протокол; MIME использует Base64, чтобы переносить всё, что текстом не является.

Ловушка с Unicode

Браузер даёт вам btoa и atob, и это ловушка. Обе работают с байтами, представленными строкой, поэтому btoa выбрасывает исключение на любом символе выше U+00FF. Попробуйте на эмодзи — получите InvalidCharacterError.

Лечится это предварительным переводом в байты UTF-8, что эта страница и делает:

// Encode any string safely
const bytes = new TextEncoder().encode(text);
const b64 = btoa(String.fromCharCode(...bytes));

// Decode back
const binary = atob(b64);
const out = new TextDecoder().decode(
  Uint8Array.from(binary, c => c.charCodeAt(0))
);

Одна оговорка к строке кодирования: разворачивание большого массива в String.fromCharCode переполняет стек вызовов где-то около ста тысяч элементов. Для крупных данных идите циклом по кускам — эта страница так и делает, потому и переваривает вставку в несколько мегабайт, не падая.

Два алфавита

Стандартный (RFC 4648 §4)URL-безопасный (§5)
Индекс 62+-
Индекс 63/_
Дополнение=, обязательноОбычно опускается
Где применяетсяMIME, PEM, Basic-аутентификацияJWT, URL, имена файлов

Хлопоты доставляет именно дополнение. Его единственная работа — довести длину до кратной четырём, а декодер всегда может вычислить, сколько его не хватает, — поэтому URL-безопасный вариант его отбрасывает. Строгие декодеры тогда отвергают результат, отчего сегмент JWT, вставленный в другой инструмент, так часто не читается. Здешний декодер восстанавливает дополнение сам.

Где его применяют неправильно

Поскольку Base64 нечитаем с первого взгляда, его используют так, будто он секрет. Стоит назвать конкретные случаи:

  • Base64 как хранение пароля. Это открытый текст в костюме. Паролям нужен медленный односторонний хеш — bcrypt, scrypt, Argon2.
  • Base64 в полезной нагрузке JWT, как будто она приватна. Нагрузку прочтёт любой, у кого есть токен, включая самого пользователя. Никогда не кладите туда того, чего не показали бы ему.
  • Base64, чтобы протащить данные мимо фильтра. Работает — недолго, а любой стоящий сканер всё равно раскодирует.

Правило, покрывающее все три случая: если безопасность чего-либо держится на том, что читатель не станет это декодировать, никакой безопасности там нет.

Смежное

Декодер JWT разбивает токен и разбирает оба сегмента за один шаг. Если речь об экранировании текста, попадающего в URL, а не о кодировании байтов, то нужное вам — это процентное кодирование, совсем другая вещь. А если раскодированное оказалось JSON, форматтер разложит его по полочкам.

Вопросы про Base64

Base64 — это шифрование?

Нет, и здесь стоит быть прямолинейным, потому что эта путаница приводит к настоящим инцидентам. Base64 — обратимое кодирование без ключа и без секрета: у кого есть строка, у того есть данные, а декодирование занимает одну строчку кода. Оно существует, чтобы проводить байты через каналы, пропускающие только печатный текст, а не чтобы что-то прятать. Хранить пароль в Base64 — значит хранить его открытым текстом с лишним шагом.

Почему многие инструменты Base64 ломаются на эмодзи и буквах с диакритикой?

Потому что браузерная функция btoa работает с байтами, а не с символами, и выбрасывает ошибку на всём выше U+00FF. Инструменты, вызывающие её напрямую, спотыкаются на é, на 日本語 и на любом эмодзи. Эта страница сначала кодирует текст в байты UTF-8 и точно так же декодирует обратно, поэтому полный цикл проходит без потерь для любого текста, который можно набрать.

Что такое URL-безопасный Base64?

В стандартном алфавите есть + и /, и оба имеют значение внутри URL, как и знак = в дополнении. URL-безопасный вариант из RFC 4648 заменяет + на - и / на _, а дополнение обычно отбрасывает. Именно он используется во всех трёх сегментах JWT, поэтому сегмент JWT, вставленный в обычный декодер, часто не читается, пока не вернуть дополнение. Декодер здесь принимает оба алфавита, с дополнением и без.

Почему закодированная строка больше исходной?

Примерно на треть, всегда. Base64 представляет три байта входных данных четырьмя печатными символами, поэтому результат в 4/3 раза длиннее ещё до дополнения. Это цена использования только тех символов, которые переживут системы, неспособные переносить произвольные байты. По той же причине встраивание картинок в CSS через data: URI делает таблицу стилей заметно тяжелее, чем были сами файлы изображений.

Я декодировал строку и получил бессмыслицу. Что произошло?

Скорее всего, эти байты вообще не текст. Base64 кодирует произвольные двоичные данные — изображение, сертификат, архив, — и превращение их в строку даёт бессмыслицу, потому что восстанавливать там нечего. Эта страница прямо об этом сообщает, а не показывает «кракозябры»: декодирование идёт со строгой проверкой UTF-8, поэтому недопустимые последовательности байтов помечаются как двоичные данные, а не заменяются молча вопросительными знаками.

Последняя проверка . Заметили, что что-то устарело? Напишите нам.