Что такое Base64 на самом деле
Base64 — это способ записать произвольные байты, используя всего 64 безопасных символа. В этом вся идея. Это не шифрование, не сжатие и не контрольная сумма. Он решает одну узкую задачу: у вас есть двоичные данные, а канал, по которому их нужно протащить, принимает только текст.
Таким каналом может быть тело письма, поле JSON, URL, HTTP-заголовок, YAML-конфиг или файл
PEM. Отправьте сырые байты — и что-нибудь по дороге их испортит: нулевой байт обрежет строку,
0x0D перепишется при конвертации переводов строк, старший байт переосмыслится в
другой кодировке. Base64 обходит всё это, используя только буквы, цифры и два знака пунктуации, и
каждый из них спокойно переживает обращение с ним как с текстом.
Цена — размер: выход примерно на треть больше входа. Такова сделка, и она почти всегда оправдана, потому что альтернатива — данные, которые приходят испорченными.
Как три байта становятся четырьмя символами
Base64 работает группами по три байта. Три байта — это 24 бита, а 24 ровно делится на четыре группы по шесть. Каждая 6-битная группа индексирует алфавит из 64 символов, поэтому три байта на входе дают четыре символа на выходе.
Возьмём три байта слова Man:
M = 77 = 01001101, a = 97 = 01100001, n = 110 = 01101110
Склеиваем биты и заново режем по шесть:
010011 010110 000101 101110 → 19, 22, 5, 46 → T, W, F, u
Значит Man кодируется как TWFu. Четыре символа, заполнение не
понадобилось, потому что три байта разделились ровно. Именно про это «разделились ровно» и
следующий раздел.
Эти 64 символа
Индексы 0–25 — это A–Z, 26–51 — a–z,
52–61 — 0–9, а 62 и 63 — те самые два, из-за которых все беды. В
стандартном Base64 это + и /.
Алфавит и почему их два
+ и / совершенно нормальны в теле письма и откровенно враждебны в
URL. / — разделитель пути, а + в строке запроса означает пробел, так что
значение Base64, вставленное в URL, может вернуться с изменёнными байтами и без единой ошибки
где-либо.
Отсюда base64url — вариант, который сохраняет первые 62 символа и меняет два
последних: - вместо + и _ вместо /. Именно эта
кодировка используется в JSON Web Token — если вы когда-нибудь читали JWT вручную, вы читали
base64url — и во всём остальном, что едет в URL или в имени файла.
В остальном варианты идентичны, и именно это делает их смешивание опасным. Строка, в которой
нет ни +, ни /, ни -, ни _, корректна в обоих
и декодируется одинаково. Строка, где они есть, декодируется в другие байты в зависимости
от того, какой вариант предположил декодер, и обычно без всякой ошибки. Наш
кодировщик и декодер Base64 сообщает,
какой алфавит он распознал, именно по этой причине.
Заполнение и почему декодеры с ним не согласны
Вход редко кратен трём байтам. Один лишний байт даёт два символа, два лишних — три, а
стандартный Base64 добивает группу до четырёх знаками =:
- 3 байта → 4 символа, без заполнения
- 2 байта → 3 символа +
= - 1 байт → 2 символа +
==
Заполнение не несёт данных. Оно существует, чтобы декодер, читающий склеенный поток, знал, где
кончается одно закодированное значение. Если вы декодируете одну отдельную строку, оно вам вовсе
не нужно — поэтому base64url обычно его отбрасывает, да и = в URL всё равно
неудобен.
Практическая сторона
Некоторые декодеры требуют заполнение и наотрез отвергают строку без него. Другие принимают
оба вида. Это самая частая одиночная причина, по которой токен работает на одном языке и падает на
другом с сухим «invalid base64». Лечится почти всегда возвратом заполнения: добавляйте
=, пока длина не станет кратной четырём. Декодер, строгий к заполнению, не сломан — и
кодировщик, который его опустил, тоже: они следуют разным половинам одной спецификации.
Base64 — не шифрование
Это заслуживает отдельного раздела, потому что ошибка очень распространённая и очень дорогая. У Base64 нет ключа. Он ничего не скрывает. Обратное преобразование — один вызов функции, доступный в любом языке и на любой странице вроде этой.
Канонический пример — HTTP Basic-аутентификация. Заголовок
Authorization: Basic — это не более чем
логин:пароль в Base64. Он выглядит непрозрачным, и в этом как раз
опасность: тому, кто ни разу его не декодировал, он читается как «зашифрованный», тогда как на
деле любой, кто видит запрос, прочитает учётные данные. Поэтому Basic допустим только по HTTPS:
секретность даёт транспорт, кодирование не даёт никакой.
То же касается нагрузки JWT, блока Base64 в конфиге и ключа API, который кто-то «обфусцировал» перед коммитом. Если между злоумышленником и вашим секретом стоит только Base64, между злоумышленником и вашим секретом не стоит ничего. Наше руководство по разбору и отладке JSON Web Token подробнее разбирает, что это значит именно для токенов.
Где Base64 встречается на практике
Как только узнаёшь характерный вид, видишь его повсюду:
- Вложения в письмах. MIME переносит двоичные данные так с начала девяностых, с переносом на 76 символов в строке.
- data: URI. Небольшая картинка, встроенная в CSS или HTML как
data:image/png;base64,…: один HTTP-запрос в обмен на треть лишних байт. - Файлы PEM. Сертификаты и ключи между метками
-----BEGIN…— это Base64 от лежащего под ними DER, с переносом на 64 символа. - JWT. Заголовок и нагрузка — это JSON в base64url; подпись — base64url от сырых байт.
- Basic-аутентификация и некоторые ключи API. Как выше, с той же оговоркой.
- Двоичные данные внутри JSON. В JSON нет типа «байт», поэтому байты едут строкой Base64.
Почему в части Base64 есть переводы строк
MIME переносит на 76 символах, PEM — на 64, потому что форматы, в которых они живут, старше длинных строк. Эти переводы не часть данных, и декодер обязан их игнорировать. Большинство игнорирует; некоторые нет, и поэтому вставка PEM-тела с переносами в строгий декодер иногда не проходит, пока переносы не убрать.
Плата за размер и когда она важна
Четыре символа на три байта — это рост в 4/3, примерно 33 %, ещё до заполнения и переносов. Для разового значения в конфиге это неважно. Для чего-либо на горячем пути — важно.
Картинка в 10 КБ, встроенная как data: URI, стоит около 13,3 КБ HTML и, в отличие от настоящей картинки, не кешируется отдельно и не загружается отложенно. Токен, уезжающий с каждым вызовом API, тащит свои накладные расходы на каждом запросе. И сжатие вас не спасёт: вывод Base64 сжимается гораздо хуже исходных байт, потому что кодирование разрушает побайтовые закономерности, которые ищет компрессор. Сначала сжимайте, потом кодируйте — никогда не наоборот.
Кодировать текст — значит кодировать байты
Base64 кодирует байты. Понятия «символ» у него нет, поэтому «закодируй эту строку в Base64» — всегда сокращение от «преврати эту строку в байты, а потом закодируй эти байты». Промежуточный шаг и есть место, где всё ломается, потому что ему нужна кодировка символов, а спецификация её за вас не выбирает.
На практике ответ — UTF-8, и современные API берут её по умолчанию. Но если одна сторона кодирует текст в UTF-16 или Latin-1, а другая декодирует как UTF-8, Base64 будет совершенно корректным, а текст на выходе — кракозябрами. Признак: не-ASCII символы переживают плохо, а чистый ASCII — прекрасно, ведь ASCII является подмножеством UTF-8.
Проверка туда и обратно
Если вы не уверены, что конвейер корректно обращается с текстом, закодируйте строку с
диакритикой и нелатинским символом, декодируйте обратно и сравните. Всё, что ломает
José или эмодзи, теряет информацию где-то в преобразовании в байты, а не в
самом Base64.
Когда декодирование не удаётся
Четыре диагноза покрывают почти все случаи:
- «Недопустимый символ». Внутрь попало что-то вне алфавита — чаще всего
случайный пробел, кавычка, скопированная вместе со строкой, или оставшийся впереди префикс
data:image/png;base64,. - «Недопустимая длина». После удаления пробелов длина по модулю четыре равна 1, а корректный Base64 такого дать не может. По дороге потерялся символ.
- Декодируется, но в мусор. Либо не совпали алфавиты (base64url раскодирован как стандартный или наоборот), либо не совпала кодировка текста, как описано выше.
- Декодируется в байты, которые вовсе не текст. Это не ошибка. Множество Base64 хранит изображение, ключ или сериализованную структуру; хороший декодер покажет вам шестнадцатеричный вид, а не сделает вид, что результат — строка.
Пройти эти пункты по порядку — символы, длина, алфавит, кодировка — решает почти всё. А когда значение просто нигде не декодируется, самое вероятное объяснение самое скучное: оно было обрезано. В Base64 нет проверки целостности, поэтому строка, потерявшая хвост, выглядит ровно так же, как строка, у которой его никогда не было.
Связанные инструменты и руководства:
- Кодировщик и декодер Base64 — Кодируйте текст в Base64 или обратно — с URL-safe выводом и корректным UTF-8.
- Декодер JWT — Декодируйте JSON Web Token и прочитайте заголовок, нагрузку, клеймы и срок действия.
- JWT простыми словами: разбор и отладка JSON Web Token — Что лежит внутри JSON Web Token
Частые вопросы
Для чего нужен Base64?⌄
Base64 проводит двоичные данные через каналы, принимающие только текст: вложения в письмах, data: URI в CSS и HTML, сертификаты и ключи в файлах PEM, двоичные поля внутри JSON и HTTP-заголовки вроде Authorization: Basic. Там, где сырые байты были бы испорчены или отвергнуты, Base64 доставит их без изменений.
Base64 — это шифрование или кодирование?⌄
Кодирование. Ключа нет и секретности нет: раскодировать — один вызов функции, который сделает любой браузер. Base64 делает байты пригодными к передаче, а не защищёнными от чтения. Никогда не используйте его для защиты пароля, токена или ключа API.
Почему моя строка Base64 заканчивается на = или ==?⌄
Это заполнение. Base64 работает группами по три байта, и если вход не делится ровно, последняя группа добивается до четырёх символов одним или двумя знаками =. Данных заполнение не несёт; base64url обычно его опускает, а некоторые строгие декодеры его требуют — это и есть обычная причина строки, которая декодируется на одном языке и не декодируется на другом.
Чем Base64 отличается от base64url?⌄
Двумя символами и заполнением. Стандартный Base64 использует + и / на двух последних позициях алфавита; base64url вместо них берёт - и _, потому что + и / имеют в URL особый смысл, и обычно отбрасывает заполнение =. JWT используют base64url. Декодирование одного как другого молча даёт другие байты.
Насколько Base64 увеличивает данные?⌄
Примерно на 33 %: каждые три байта превращаются в четыре символа, плюс заполнение и переносы строк. Если размер важен, сжимайте до кодирования, а не после: вывод Base64 сжимается плохо, потому что кодирование разрушает побайтовые закономерности, на которые опирается компрессор.
Более 15 лет практического опыта в веб-разработке и безопасности. Специализация — практическая веб-безопасность: чтобы сайты были защищёнными, надёжными и легко диагностировались. BuildToolForge создан, чтобы такие проверки были бесплатными, приватными и понятными. Инструменты по возможности работают прямо в браузере, а всё, что вы вводите в инструмент, не сохраняется.
Как мы пишем руководства: Каждое руководство основано на практическом опыте, сверено с официальными стандартами и проверено нашими инструментами на реальных доменах. Мы обновляем руководства, когда меняются стандарты или инструменты. Нашли ошибку? Напишите нам: contact@buildtoolforge.com.