Некоторое время назад у меня был пример использования, когда base64 спас мою задницу, и я подумал, что напишу для себя краткое освежение информации о том, что он может сделать, и как он может быть полезным инструментом для любого инженера-программиста.

Во-первых, base64 — это набор символов (помните такие?). В этом наборе 64 символа: 26 прописных и 26 строчных английских букв (A — Z, az), 10 цифр (0-9) и два других символа (обычно + а также /), всего 64 символа. Каждый символ здесь соответствует числу от 0 до 63.


Кодирование

Что еще более важно, base64 — это байтовая кодировка. Вы даете ему набор байтов, и он преобразует их в строку, содержащую только эти 64 символа (а в некоторых случаях и = знак). Обратите внимание, что эти байты не обязательно должны быть просто текстом. Вы можете указать файл или любую случайную последовательность байтов. Целью кодирования является представление любого потока байтов в виде печатных символов ASCII, поскольку они «просты» и поддерживаются большинством компьютеров (в западном мире).

В каждом языке программирования и ОС есть куча инструментов для конвертации в/из base 64, но давайте сделаем это сами. Как мы кодируем строку, например hi в базе64?

Во-первых, забудьте о «строковой» части. База 64 работает с байтами, поэтому не важно, является ли ввод строкой. Первое, что нужно сделать, это записать это как последовательность байтов. В Юникоде/ASCII, h это кодовая точка 104₁₀ и i составляет 105₁₀. (Я использую ₁₀, чтобы указать, что это числа с основанием 10.) Теперь мы запишем их как числа с основанием 2:

01101000 01101001
Войти в полноэкранный режим

Выйти из полноэкранного режима

А теперь кодируем!

  1. Мы собираем все вместе, затем группируем их в 6-битные группы (добавляем 0 в конце, если бит недостаточно).
  2. Возьмите каждую группу как новое число и найдите ее в алфавите base64. Например, если число 3, мы заменяем его числом 3 в base64, то есть D (A = 0, B = 1…). Полный набор символов здесь.
// Put everything together
0110100001101001

// Group them by 6 bits. Last group has only 4, so add two zeros.
011010 000110 100100

// Convert them to base64 numbers
011010 = 26₁₀ = a₆₄
000110 = 6₁₀ = G₆₄
100100 = 36₁₀ = k₆₄
Войти в полноэкранный режим

Выйти из полноэкранного режима

Так hi является aGk в кодировке base64. Скорее всего, вы увидите, что это написано как aGk=потому что строки base64 часто дополняются одним или двумя =s, чтобы убедиться, что их длина кратна 4 (например, для проверки полноты полученной строки).


Сценарии использования

Важной особенностью base64 является то, что он может представлять любой байт в этих «обычных» текстовых символах, что делает его полезным для множества вещей:


Передача двоичных данных

Вы можете использовать это для передачи нетекстовых данных (таких как файл) через механизмы, предназначенные для текста. Эти механизмы обычно ломаются или приводят к повреждению данных, потому что вы передаете им нетекстовые байты. Кодируя в base64, вы можете безопасно отправлять такие данные, а затем декодировать их, чтобы получить исходный файл без каких-либо потерь.

Собственно, это и было моей первоначальной потребностью. Я работал с удаленным сервером, и мне нужно было создать PDF-файл на сервере и проверить его содержимое. У меня был доступ только к терминалу, поэтому я не мог открыть на сервере модное приложение, такое как Adobe Reader. У меня не было доступа к SCP или другим подобным инструментам. Мне пришлось сгенерировать на сервере, затем загрузить его на свою машину и открыть локально. С base64 это было:

heroku run rails r "puts Base64.encode64(thing.generatePdf.string)" | base64 --decode > file.pdf
Войти в полноэкранный режим

Выйти из полноэкранного режима

(Это пример на основе Ruby, но везде применимо одно и то же.)

heroku run rails r bit — это код-оболочка для запуска этой команды в моем удаленном приложении Rails через Heroku. Мы запускаем thing.generatePdf метод, который возвращает StringIO экземпляр, который можно сохранить в файл, но в этом случае мы вызываем .string на нем, чтобы прочитать содержимое в виде строки. Затем мы передаем это кодировщику base64 и выводим на стандартный вывод (puts). Труба (|), затем передает этот вывод в base64 утилита на моей машине, которая декодирует его и сохраняет в file.pdf. И точно так же я мог открыть файл локально.

(Мне стыдно признаться, что сначала я попытался просто напечатать исходную строку прямо в файл на моем компьютере, но это не сработало. Файл был поврежден. В этом есть смысл, поскольку в PDF-файлах есть много байтов, которые не текст, а терминалы и SSH предназначены для текста.)

Один трюк, который вы можете сделать с этим, — это загрузка файлов JSON. JSON — это текстовый формат, поэтому в нем нельзя загружать файлы. Вам придется переключиться на составные тела запросов. Но если вы действительно хотите придерживаться JSON, вы можете закодировать файл в base64 и передать его как обычную строку. (Будьте осторожны, это, вероятно, будет очень длинная строка.)

{
  "photo": "N7c2PJAxPJ9mIGFueSBjYXJuYWwgcGxlYXN1cmUu..."
 }
Войти в полноэкранный режим

Выйти из полноэкранного режима

Вы, вероятно, не должны, хотя, но мы придем к этому.


Как обойти нежелательные символы

Другая причина, по которой вы можете захотеть использовать base64, — это безопасная сериализация строк. Например, предположим, что у вас есть произвольная строка, и вы хотите сериализовать ее как строку в строку в кавычках, как в фрагменте кода:

const str = "Some string";

const codeSnippet = `const x = "${str}";`;
Войти в полноэкранный режим

Выйти из полноэкранного режима

Это создает неверный код, если str содержит двойные кавычки:

const str = 'There are "quotes" here.';

const codeSnippet = `const x = "${str}";`;
// Output:
// const x = "There are "quotes" here";
Войти в полноэкранный режим

Выйти из полноэкранного режима

Один из способов обойти это — вручную экранировать кавычки перед интерполяцией. Другой вариант — использовать закодированную версию, которая гарантированно нет есть цитаты. (Я научился этому трюку от Калеб Порцио).

const str = 'There are "quotes" here.';

const codeSnippet = `const x = "${btoa(str)}";`;
// Output:
//'const x = "VGhlcmUgYXJlICJxdW90ZXMiIGhlcmUu";
Войти в полноэкранный режим

Выйти из полноэкранного режима

К сожалению, здесь вы теряете читабельность, поэтому, если это важно для вас, вам, вероятно, следует сбежать вручную.


Сериализация в строку

Еще одно применение, которое я недавно использовал, — это репликация объекта в памяти. По сути, я хотел сделать копию какого-то объекта (например, экземпляра Person класс и связанные объекты) из одной среды и импортировать его как полностью сформированный объект в другую. Эти объекты были большими, поэтому у меня не было времени вручную копировать каждое свойство. Я вспомнил сериализация, который был разработан для таких вещей. Я мог бы просто сериализовать объекты в одной среде, скопировать вывод и десериализовать его в другой:

// in one environment
$object = (...); // instance of Person
echo serialize($object);

// in another environment
$input = trim(fgets(STDIN));
$object = unserialize($input); // instance of Person
Войти в полноэкранный режим

Выйти из полноэкранного режима

# Pipe serialized output from (1) as input to (2)
php env1.php | php env2.php
Войти в полноэкранный режим

Выйти из полноэкранного режима

Это работает в PHP, потому что алгоритм сериализации PHP создает только текст. Но Ruby создает нетекстовые байты (как объяснено в моем посте на сериализация), так что это сломается. Решение? База64.

object = (...)
puts Base64.encode64(Marshal.dump(object))

input = gets.strip
object = Marshal.load(Base64.decode64(input))
Войти в полноэкранный режим

Выйти из полноэкранного режима


Недостатки и на что обратить внимание

  • Вывод Base64 больше, чем ввод. Каждые 3 байта данных кодируются в 4 байта данных, так что размер увеличивается примерно на 33%.
  • Ты Можно используйте base64 для принудительной загрузки файлов через JSON, но вы, вероятно, не должны. Он занимает больше данных и включает ненужную дополнительную обработку (кодирование и декодирование). Multipart — это схема, разработанная для двоичных файлов через HTTP, так что пользуйтесь ей.
  • Base64 не является механизмом безопасности. Кодирование не является шифрованием. Любой может прочитать строку Base64. JWT закодированы в base64, но это просто для удобства транспортировки. Любой, кто завладеет вашим JWT, сможет расшифровать его и прочитать информацию, которую вы в нем храните, поэтому будьте осторожны с тем, что вы там храните.
  • Base64 не полностью безопасен для URL; вы можете поместить его в URL, но / символ может быть неверно истолкован, поэтому его часто необходимо кодировать. Если вам нужно что-то, что можно поместить в URL как есть, другие кодировки (например, base32) могут подойти лучше.

Кодировки — это весело. Base64 — это не кодировка символов, в отличие от UTF-8; это скорее «кодирование данных». Вот интересный короткий пост на некоторых разных вещах, которые мы знаем как «кодировки».