Приходится иметь дело с форматом Base64? Тогда этот сайт идеально вам подойдет! Воспользуйтесь нашим невероятно удобным онлайн-инструментом для кодирования или декодирования ваших данных.

Кодирование Base64 в Bash: полное руководство

У вас есть байты, а нужна строка. Текстовый файл, которому суждено жить внутри JSON-тела. Изображение, которому положено сидеть в строке конфигурации. Токен, который поедет через URL, переменную окружения или HTTP-заголовок. Закрытый ключ, который должен лежать в хранилище сертификатов. Это повседневная работа Base64-кодирования в shell, и ответ shell - одна маленькая, на удивление переносимая команда.

Сделка в одном дыхании: Base64 переписывает каждые три байта сырых данных в четыре символа из 64-символьного алфавита (A-Z, a-z, 0-9, плюс + и /), дописывая в хвост один или два знака =, если количество байтов не кратно трём. Главная страница этого сайта объясняет формат целиком; здесь мы тратим время на производство текста, выбор правильного диалекта для адресата и оплату счёта за размер открытыми глазами. Одно число стоит держать в кармане: закодированная форма обычно примерно на треть больше оригинала, четыре символа на три байта, и это число возвращается к нам снова и снова.

Ансамбль небольшой. Команда base64 из coreutils (GNU или более новое Rust-семейство uutils), basenc из того же семейства для URL-безопасного диалекта, openssl base64 для машин без coreutils, апплет BusyBox для встраиваемых систем и BSD-версия на macOS. Пять инструментов, одна работа, пара флагов, которые стоит знать.

Выберите свой кодер

Каждый из этих инструментов читает байты из стандартного ввода или из файла и пишет текст в стандартный вывод, так что все они встают в одни и те же конвейеры. Разница - в переносе строк по умолчанию и в доступных диалектах:

Инструмент Где живёт Перенос строк по умолчанию Когда тянуться за ним
base64 (coreutils) Linux, и macOS через Homebrew 76 символов выбор по умолчанию; для одной строки добавьте -w 0
basenc (GNU coreutils) Linux с coreutils 76 символов нужны --base64url, base32, base16 или их собратья
openssl base64 везде, где установлен OpenSSL 64 символа coreutils нет; для одной строки - -A
busybox base64 Alpine, встраиваемый Linux 76 символов минимальные системы; те же флаги в меньшем корпусе
base64 (BSD/macOS) macOS, BSD-системы нет (одна длинная строка) нативная работа на macOS; -b задаёт ширину

Прочитайте колонку про перенос дважды, потому что в ней - тихое различие между семействами. Coreutils и BusyBox по умолчанию переносят по 76, OpenSSL - по 64, а BSD-инструмент не переносит вовсе. Никто из них не ошибается; просто они унаследовали разные конвенции (MIME говорит 76, PEM - 64, а BSD-инструмент просто старше привычки к переносу). Когда вашему потребителю это важно, задавайте ширину явно и никогда не полагайтесь на значение по умолчанию.

Сначала текст: невидимый перевод строки

Кодирование текста в shell начинается с одной ловушки: echo добавляет перевод строки. Пять букв «hello» превращаются в шесть байтов в тот миг, как проходят через echo, и шестой байт незримо и навсегда заезжает в вывод:

echo "hello" | base64

Вывод - aGVsbG8K, и последний символ кодирует перевод строки. Исправление - то самое, которое стоит использовать для текста, где количество байтов имеет значение: printf с форматом, без украшений:

printf '%s' "hello" | base64

Теперь вывод - aGVsbG8=, ровно на пять байтов, и последний символ - знак заполнения, а не живой байт. То же правило касается here-strings, которые добавляют завершающий перевод строки, как и echo: base64 <<< "hello" снова даёт версию aGVsbG8K. Когда сомневаетесь, спросите себя, что за байт последний, прежде чем его кодировать.

Для всего, что нужно на одной строке, добавьте -w 0 (или собратьев -w 0 ниже), что также убирает завершающий перевод строки, который команда иначе выпустила бы:

printf '%s' "hello world and more" | base64 -w 0

Это одна чистая, неразрывная строка без завершающего перевода строки, готовая лечь в URL, значение JSON или файл конфигурации без каких-либо дальнейших церемоний.

Файлы и ширина переноса

Файлы - частый случай, и каждая реализация принимает аргумент FILE, что полностью держит байты вдали от механизма кавычек shell:

base64 -w 0 report.pdf > report.b64

Без -w 0 вывод приходит сложенным по 76 символов, и это ровно то, что MIME-потребителю и нужно:

base64 report.pdf > report.mime.b64

Ширина - ручка, которую вы выставляете под каждого потребителя. Семьдесят шесть - MIME-конвенция из RFC 2045, шестьдесят четыре - PEM-конвенция, которой пользуются сертификаты и ключи, а ноль - одна неразрывная строка для URL и API:

base64 -w 64 key.bin | head -2

Если потребитель живёт в Windows и ждёт окончания строк CRLF, конвертируйте после переноса, а не до него:

base64 -w 76 attachment.bin | sed 's/$/\r/' > attachment.crlf.b64

Для пути OpenSSL эквивалент однострочного режима - флаг -A, который к тому же подавляет завершающий перевод строки:

openssl base64 -A < report.pdf

Прежде чем отправлять сложенный файл, проверка размера не стоит ничего и ловит удивительное число ошибок (файл, закодированный дважды, файл, закодированный из неверного ввода):

wc -c report.pdf
base64 report.pdf | wc -c

Второе число должно быть примерно в четыре трети от первого, плюс один байт на каждую сложенную строку за переводы строк. Если оно сильно отличается, остановитесь и посмотрите, что вы на самом деле подали кодеру.

URL-безопасный Base64: подмена двух нехороших символов

Два символа в стандартном алфавите, + и /, - хулиганы: + в query-строке URL означает пробел, / может выглядеть как разделитель пути, и оба требуют процентного кодирования в тот миг, как строка попадает в URL, cookie или имя файла. Раздел 5 RFC 4648 решает это диалектом, который заменяет ровно эти два символа на - и _ и выбрасывает заполнение, потому что URL редко нуждается в том, чтобы сообщать точную длину в байтах.

Рецепт в shell - подмена плюс обрезка: один проход через tr для алфавита и один - чтобы убрать заполнение:

printf '\376\117\202' | base64 -w 0 | tr '+/' '-_' | tr -d '='

Эти три байта обычно кодируются в /k+C, что было бы некрасиво в URL; конвейер превращает это в _k-C, четыре символа, которые могут уехать куда угодно. Подмена позиционная, поэтому направление легко перепутать: кодирование идёт tr '+/' '-_' (плюс становится дефисом, слэш - подчёркиванием), а обратное, которое принадлежит декодированию, идёт tr '_-' '/+'. Перепутанное направление не даёт ошибку, оно просто производит другие байты, а это худший сорт багов для отправки.

Диалект важен всякий раз, когда строка выходит из-под контроля shell: сегменты JWT, токены в query-строках, значения в cookie или именах файлов и любой идентификатор, который другая система прочитает как часть URL. basenc от GNU производит диалект нативно, при этом заполнение ещё на месте:

printf '%s' "hello" | basenc --base64url

Обрежьте заполнение через tr -d '=', если потребитель хочет обрезанную форму, как это делают большинство.

Чеканка JWT в shell

JSON Web Token - самый заметный потребитель URL-безопасного Base64 в мире API. Компактный JWT - это, по RFC 7515, три base64url-сегмента, склеенные точками: заголовок, payload и подпись. Два первых - обычный JSON; подпись - бинарный дайджест двух первых сегментов, склеенных точкой, а это ровно то, что openssl умеет хорошо.

key="supersecretkey"
h=$(printf '%s' '{"alg":"HS256","typ":"JWT"}' | base64 -w 0 | tr '+/' '-_' | tr -d '=')
p=$(printf '%s' '{"sub":"42","name":"homer"}' | base64 -w 0 | tr '+/' '-_' | tr -d '=')
s=$(printf '%s' "$h.$p" | openssl dgst -sha256 -hmac "$key" -binary | base64 -w 0 | tr '+/' '-_' | tr -d '=')
printf '%s.%s.%s\n' "$h" "$p" "$s"

Вывод - компактный HS256 JWT, который примет любая стандартная библиотека на любой платформе. Обратите внимание на разделение труда: Base64-часть - это алфавит, часть openssl dgst -sha256 -hmac - это криптография, а склеивание точками - это формат. Держите три работы раздельными в голове, и конвейер останется очевидным.

Три предостережения для поля. Во-первых, MAC считается по ASCII-тексту двух первых сегментов плюс точка, так что к моменту подписи сегменты должны уже быть в своей финальной base64url-форме; повторный перенос или повторное заполнение после подписи сломают токен. Во-вторых, ключ остаётся вне токена: подпись доказывает, кто подписал, а ключ хранит секрет секретом. В-третьих, чеканка в shell-скрипте - инструмент для тестов и автоматизации, а не замена серверу, который будет по-настоящему выпускать и проверять эти токены, и токен, чеканенный с alg: none, не доказывает вообще ничего.

Data URI: файлы, путешествующие внутри строк

RFC 2397 определяет схему URL data:, и её Base64-форма позволяет файлу жить внутри URL: data:, затем необязательный media type, затем ;base64, когда payload закодирован в Base64, затем запятая, затем данные. Если опустить media type, по умолчанию получится text/plain;charset=US-ASCII, а это ловушка, которую стоит знать: обычно имеют в виду изображение или JSON-документ, а не ASCII-текст.

printf 'data:text/plain;base64,%s\n' "$(printf '%s' "hi there" | base64 -w 0)"

Вывод - data:text/plain;base64,aGkgdGhlcmU=, полная, самодостаточная URL, которую браузер охотно отобразит. Для изображения - та же форма с настоящим media type:

printf 'data:image/png;base64,%s\n' "$(base64 -w 0 icon.png)" > icon.uri

Вставьте результат в HTML-тег img, в его src, или в CSS background, и изображение поедет вместе с документом, без второго HTTP-запроса. Все ловушки про размер: сам RFC говорит, что схема полезна только для коротких значений, браузеры налагают собственные лимиты длины URL, каждый вставленный байт стоит 33 процента надбавки к собственному размеру изображения, а страница, полная data URI, - это страница без истории кэширования для этих изображений. Для маленьких иконок и разовых встроенных графиков - радость, для фотобанка - налог.

Секреты, конфигурация и переменные окружения

Base64 появляется в работе с конфигурацией и секретами по одной конкретной причине: он превращает любые байты, включая пробелы, кавычки и переводы строк, в строку, которая переживает export, строку конфигурации или поле JSON без каких-либо акробатик с кавычками. Kubernetes - самый видимый пример: каждое поле под .data секрета - это Base64, так что создание секрета в shell - это просто кодирование:

kubectl create secret generic app --from-literal=password='s3cret'

API-сервер хранит пароль как czNjcmV0 под .data, и любой узел с доступом к секрету может прочесть его обратно одним декодированием. Тот же приём работает для собственных файлов конфигурации:

export API_TOKEN_B64=$(printf '%s' "$API_TOKEN" | base64 -w 0)

Или для файла, который приложение читает при запуске:

printf 'token=%s\n' "$(printf '%s' "$API_TOKEN" | base64 -w 0)" >> app.conf

И вот предупреждение, которое положено развесить по стенам: Base64 - это кодирование, а не шифрование. Раздел безопасности RFC 4648 говорит об этом откровенно, отмечая, что кодирование «визуально скрывает информацию, которую иначе легко распознать, например пароли, но не обеспечивает никакой вычислительной секретности», и что именно это непонимание вызывало реальные инциденты безопасности, когда кто-то вставлял «защищённый» обмен протоколом в отчёт об ошибке и случайно выкладывал учётные данные. Если значение должно быть секретным, зашифруйте его (а затем закодируйте шифротекст в Base64 для хранения); если у вас есть только Base64, считайте закодированное значение обычным текстом с того момента, как оно покидает экран.

Unicode, кодировки и байты под ними

Кодер читает байты, а не символы, и shell передаёт ему те байты, которые произвели локаль и команда. Для UTF-8-текста это обычно ровно то, что вам нужно: é в héllo - уже два байта, c3 a9, и кодирование просто возит их с собой:

printf 'h\xc3\xa9llo' | base64

Вывод - aMOpbGxv, и UTF-8-потребитель на том конце получает héllo обратно, байт в байт. Беды начинаются, когда источник не в UTF-8. Latin-1-файл с тем же словом держит для é одиночный байт e9, и кодирование этих байтов как есть производит текст, который обратно прочитает только Latin-1-потребитель. Сначала конвертируйте, потом кодируйте:

iconv -f ISO-8859-1 -t UTF-8 note.txt | base64 -w 0

Ещё два факта на уровне байтов. UTF-8 BOM, три байта в начале файла, кодируется в 77u/ и будет сидеть в начале вашего декодированного вывода вечно, пока вы не срежете его первым делом:

sed '1s/^\xef\xbb\xbf//' file.txt | base64 -w 0

И локаль никогда не меняет само кодирование, потому что кодер - байтовая машина; она меняет только то, что вы напечатали. Если вывод выглядит неправильно, проверяйте байты, которые вы подали, а не кодирование, которое вы запустили.

Почта, API и загрузки

Почта - место, где Base64 научился манерам, и манеры до сих пор остаются конвенцией. SMTP исторически возил только 7-битный ASCII, поэтому вложения едут как Base64, сложенный по 76 символов с окончаниями строк CRLF, по RFC 2045. Выдать MIME-части ровно эту форму - значит перенос плюс конвертация окончаний строк:

base64 -w 76 attachment.bin | sed 's/$/\r/' > attachment.mime

Старая гвардия всё ещё на службе в встраиваемых системах: uuencode от BusyBox с флагом -m производит MIME Base64, завёрнутый в привычный begin-base64-фрейминг, а его братишка uudecode читает его обратно:

busybox uuencode -m photo.jpg < photo.jpg > photo.uu

API и загрузки используют ту же идею в JSON-одежде: бинарник становится Base64-строкой внутри JSON-поля, а curl везёт её. Сборка тела в shell-переменной держит кавычки честными:

body="{\"file\":\"$(base64 -w 0 upload.bin)\"}"
curl -fsS -X POST https://httpbin.org/post -H "Content-Type: application/json" -d "$body"

Здесь живут две ловушки совместимости. Во-первых, проверьте, какой алфавит хочет API: одни ждут стандартный Base64, другие - URL-безопасный диалект, и строка с символами +, отправленная на URL-безопасный эндпоинт (или наоборот), не пройдёт валидацию или, что хуже, декодируется в неверные байты. Во-вторых, следите за двойным кодированием - классическим багом, когда скрипт кодирует значение, которое сервер кодирует снова, и круговой путь требует двух декодирований, чтобы распутаться.

Когда payload становится большим

Кодер, как и декодер, - потоковая машина: он читает кусками и пишет кусками, так что tar-архив на 10 ГБ не требует 13 ГБ RAM, и команда охотно работает минутами на больших вводах с плоским потреблением памяти. Математика размеров - единственный инструмент планирования, который вам нужен: вывод - четыре символа на три входных байта плюс один байт на каждую сложенную строку, так что файл в 300 МБ становится примерно 400 МБ текста. Быстрая проверка реальности для любого файла:

base64 -w 0 big.bin | wc -c

Когда сам текст нужно переместить по каналу с лимитом размера (лимит вложения письма, система тикетов, сообщение в мессенджере), режьте закодированную форму, никогда сырой бинарник, чтобы каждый кусок оставался обычным текстом, который можно вставить, сжать или переслать:

base64 -w 0 big.bin | split -b 4000 - part_

Это даёт серию частей по 4000 символов; получатель скатывает их обратно по порядку и декодирует один раз. И когда payload сжимаемый, сжимайте до кодирования, потому что Base64 добавляет избыточность поверх того, что данные уже содержат: tar-архив каталога проекта обычно сжимается под gzip в несколько раз, прежде чем на него наложится 33-процентная Base64-надбавка:

tar czf - project/ | base64 -w 0 > project.b64

Скорость не станет вашим ограничением. Эти кодеры пропускают гигабайты меньше чем за секунду на современной машине; файл в 200 МБ занимает примерно десятую долю секунды с реализациями coreutils и OpenSSL, а даже BusyBox, самый медленный из распространённых, всё равно справляется за долю секунды (замеры: примерно четверть секунды на 200 МБ на одной современной машине, в несколько раз медленнее coreutils, но никак не узкое место). Узкое место в реальных конвейерах почти всегда сеть, а не кодирование.

Маленькие персонажи, которые кусаются

Ловушки стороны кодирования меньше, чем у стороны декодирования, и это только справедливо:

Ловушка Что происходит Как починить
echo на вводе кодера завершающий перевод строки заезжает в вывод, и последний символ кодирует его printf '%s' для текста, где количество байтов имеет значение
Упование на перенос по умолчанию 76, 64 или ноль - в зависимости от инструмента; однострочный потребитель захлёбывается на сложенном вводе явно задать -w 0 (или ширину, которую хочет потребитель)
Завершающий перевод строки в выводе режимы со сложенными строками заканчиваются переводом строки, который при захвате загрязняет URL и JSON -w 0 для одной строки, либо захват через $(...), который срезает его
+ или / в URL в query-строке плюс читается как пробел; оба принудительно требуют процентного кодирования для всего, что входит в URL, использовать URL-безопасный диалект
Перепутанное направление tr подмена производит валидные, но неверные байты, ошибки нигде нет кодирование - tr '+/' '-_'; декодирование - tr '_-' '/+'
Кодирование уже закодированного значения двойное кодирование, которое распутывается двумя декодированиями перед кодированием проверьте, не Base64 ли источник уже
UTF-8 BOM во вводе три лишних байта в начале каждого декодированного вывода сначала срезать BOM: sed '1s/^\xef\xbb\xbf//'
Хранение настоящего секрета как Base64 одна команда отменяет его; в RFC зафиксированы реальные инциденты утечки учётных данных для секретности - шифрование, Base64 - только для формы транспорта
Предполагать алфавит потребителя несовпадение стандартного и URL-безопасного алфавитов не проходит валидацию или декодируется неверно прочитать документацию API; кодировать в диалекте, который просит потребитель

Привычки, которые держат вас в безопасности

  • Называйте число байтов. Для текста - printf '%s', для файлов - аргумент FILE, и проверка wc -c прежде чем отправлять что-либо, где размер имеет значение.
  • Задавайте перенос явно. Для URL и JSON - -w 0, для MIME - -w 76, для PEM - -w 64. Никогда не оставляйте ширину на откуп умолчанию инструмента.
  • Выбирайте алфавит под адресат. Для почты и файлов - стандартный, для токенов и URL - URL-безопасный, и читайте документацию потребителя до кодирования.
  • Сжимайте до кодирования. Для любого сжимаемого payload сначала gzip или tar czf; 33-процентная надбавка ляжет на то, что вы отдадите кодеру.
  • Резайте текст, а не бинарник. Когда впереди лимит размера, split-ьте закодированную форму, чтобы каждый кусок оставался безопасным для вставки, и собирайте по порядку до единого декодирования.
  • Держите три работы JWT раздельными. Алфавит, криптография, формат: закодируйте сегменты, подпишите ASCII-текст склеенных сегментов, затем выведите. Поменяйте порядок - и токен сломается.
  • Никогда не позволяйте Base64 подменять шифрование. Если значение секретное, зашифруйте его и затем закодируйте шифротекст. Если оно не секретное, скажите об этом и перестаньте переживать.

Короткая история кодирования в shell

  • 1980, Беркли. Мэри Энн Хортон пишет uuencode и uudecode в Калифорнийском университете в Беркли, чтобы провозить бинарные файлы между Unix-системами через почту. Имя, «от Unix к Unix», - это свидетельство о рождении формата, и следующие лет десять это то, чем кодируют shell-пользователи.
  • Эра модемного доступа. uuencode на UNIX и BinHex на TRS-80 и Apple II, за которыми на шаг позади - Macintosh, решают одну и ту же проблему разными алфавитами, каждый доверяя только символы, которые может напечатать собственный терминал.
  • 1993. MIME стандартизует Base64 для почты в RFC 1521, позже RFC 2045, с переносом строк по 76 символов, который значение по умолчанию в coreutils носит и поныне.
  • До 2006 на Linux. Команды base64 нет. Shell-скрипты тянутся за openssl base64, uuencode -m, Perl или Python, и привычка OpenSSL настолько глубока, что половина старых однострочников в дикой природе всё ещё начинается с неё.
  • 15 августа 2006. coreutils 6.0 выпускает команду base64, а файл NEWS благодарит её как «base64 encoding and decoding (RFC 3548) functionality», и начинается эра одной команды. Пару месяцев спустя, в октябре 2006, RFC 4648 формализует семейство алфавитов, включая URL-безопасный диалект, за который эта статья тянется постоянно.
  • OS X 10.7. macOS выпускает собственный base64, BSD-версию без переноса по умолчанию, и поэтому «просто запусти base64» требует проверки платформы в переносимых скриптах.
  • 2024. coreutils 9.5 меняет то, как декодеры обращаются с вводом без заполнения и неканоническим вводом, что на практике означает бесплатный проезд для кодеров: вывод, который старые версии GNU отклоняли, теперь декодируется чисто. Кодировочная сторона формата - устойчивая, а вот декодеры - те, кто двигался.
  • 2025. Переписанные на Rust coreutils (uutils) становятся стандартными в актуальных выпусках Ubuntu. Та же команда, те же флаги, новый движок и те же 76 символов по умолчанию, унаследованные от C-версии.

Маленькие чудеса

  • Имя формата истинно на каждой машине. printf 'base64' | base64 даёт YmFzZTY0 на GNU, uutils, BusyBox, OpenSSL и macOS в равной степени. Это истинно с 2006 года и всегда будет так.
  • Файл из ничего кодируется в стену из A. Подайте ему три байта NUL, и вывод будет AAAA, потому что три нуля отображаются в четыре нуля по индексу в алфавите, и каждый из них обозначается буквой A. Файл .b64, начинающийся с длинного ряда A, - это обычно нулевая отсечка в исходном файле (байты NUL), а не загадка.
  • 33-процентный налог не имеет скидок. Четыре символа на три байта, без сжатия, без второго шанса. Единственный выход - сначала сжать данные, и именно поэтому tar czf - настоящий герой конвейеров с большим payload.
  • Все URL-беды - от двух символов. + и / - единственные члены алфавита, для которых когда-либо понадобилась замена, и целый диалект формата существует, чтобы отправить их на пенсию. Шестьдесят второй и шестьдесят третий, последние два слота алфавита.
  • Одиннадцать символов, шестьдесят четыре бита. ID видео на YouTube - это 11-символьная base64url-строка, 64-битное число в URL-одежде, и именно поэтому оно путешествует по URL без единого процентного знака.
  • Самый известный проходимец в git. Бинарные блоки в git diff --binary выглядят как Base64, но строки с префиксом z - это base85-подобный диалект собственного почерка. Один взгляд - и вы понимаете, что это не ваш алфавит; один обходной путь grep-и-декода - и вы теряете двадцать минут.
  • Каждый инструмент переносит по-своему, и это намеренно. 76 для MIME, 64 для PEM, ноль у BSD-инструмента: три значения по умолчанию, три унаследованные конвенции, один формат. Ширину вы всегда могли выбрать сами; инструменты просто запомнили разные значения по умолчанию.
  • Кодер никогда не падает на ваших данных. В отличие от своего декодирующего родственника, у кодера нет невалидного ввода, нет повреждения, нет строгого режима. Он берёт байты и выдаёт буквы, каждый раз. Все баги этой статьи - в байтах, которые вы ему передаёте, и в адресате, куда вы их отправляете.

И когда путь указывает в обратную сторону, когда в терминале оказываются длинная строка из букв, цифр и изредка попадающихся дефисов или подчёркиваний и вам нужны байты обратно, связанная статья о Base64-декодировании, ссылка на неё ниже, покрывает этот ритуал с той же глубиной, от JWT-сегментов без заполнения до каждой ловушки переводов строк, которую прячут декодеры.

Последнее обновление: 2026-09-08

Связанная статья: Декодирование Base64 в Bash: полное руководство