Кодирование и декодирование 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, поэтому недопустимые последовательности байтов помечаются как двоичные данные, а не заменяются молча вопросительными знаками.
Последняя проверка . Заметили, что что-то устарело? Напишите нам.
