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

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

У ваших данных есть место назначения, которое не принимает их вид. Изображение, которому придётся жить внутри JSON-документа. Токен, которому придётся пересечь URL. Вложение, которое должно пережить протокол, спроектированный под семибитный текст. Секрет, которому нужно уместиться в переменной окружения, не сломав кавычки. В каждом из этих мест что-то посередине вот-вот уничтожит ваши двоичные данные - и у исправления есть имя: Base64.

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

Быстрый повтор перед стартом: Base64 переписывает данные по три байта за раз, выдавая четыре символа из 64-символьного алфавита, с одним или двумя знаками = в качестве заполнения, когда вход не делится на три без остатка, - и именно поэтому вывод оказывается примерно на треть больше входа. Домашняя страница этого сайта полностью покрывает формат, так что эта статья ограничивается одним выдохом на разговор о формате и сразу берётся за работу.

Какой кодировщик вам нужен?

Ruby даёт вам три кодировщика, и выбор между ними - викторина из трёх вопросов: может ли вывод содержать переводы строк? Может ли он содержать + или /? Может ли он содержать заполнение? Вот весь состав:

Кодировщик Форма вывода Переводы строк Заполнение Когда тянуться к нему
Base64.strict_encode64(bin) одна строка, стандартный алфавит никогда всегда присутствует JSON, токены, API, файлы - безопасный дефолт
Base64.encode64(bin) несколько строк, стандартный алфавит после каждых 60 символов, плюс конечный всегда присутствует тела писем и другие ориентированные на строки текстовые протоколы
Base64.urlsafe_encode64(bin, padding: true) одна строка, алфавит дефис-подчёркивание никогда ваш выбор, включено по умолчанию всё, что попадает в URL, cookie или идентификатор

Если вы решаете под давлением времени, короткий ответ такой: strict_encode64 по умолчанию, urlsafe_encode64, когда результат будет путешествовать внутри URL, и encode64, только когда получатель - текстовый протокол, который хочет короткие строки. Всё, что ниже, объясняет, почему, и где каждый выбор молча что-то стоит.

strict_encode64: рабочая лошадка

Base64.strict_encode64 - это кодировщик, который вы будете использовать в подавляющем большинстве кода. Он выдаёт ровно одну строку, всегда с правильным заполнением, из стандартного алфавита:

require "base64"
Base64.strict_encode64("hello world")
# => "aGVsbG8gd29ybGQ="
Base64.strict_encode64("s")
# => "cw=="

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

Длина входа Длина вывода Заполнение в конце
3n байтов (делится без остатка) 4n символов нет
3n + 1 байт 4n + 4 символа два знака =
3n + 2 байта 4n + 4 символа один знак =

Так 11 байтов становятся 16 символами, 100 байтов - 136, а файл в 1 мегабайт - примерно 1.33 мегабайта текста. Этот прирост в треть - входная плата за каждый пелод Base64, который вы отправляете, и это число стоит держать в кармане всякий раз, когда колонка, кэш или лимит частоты API начинают давить.

Base64.strict_encode64("123")
# => "MTIz"        3 байта на входе, 4 символа на выходе
Base64.strict_encode64("1234")
# => "MTIzNA=="   4 байта на входе, 8 символов на выходе, два знака дополнения
Base64.strict_encode64("12345")
# => "MTIzNDU="   5 байтов на входе, 8 символов на выходе, один знак дополнения

encode64: тот, что добавляет переводы строк

Base64.encode64 - классика, и у него есть поведение, погубившее не один рабочий день: оно переносит вывод на строки. Каждые 60 символов начинается новая строка, и вывод всегда заканчивается конечным переводом строки:

Base64.encode64("hello world")
# => "aGVsbG8gd29ybGQ=\n"
Base64.encode64("*" * 46)
# => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n"

Перенос - не баг: это черта, унаследованная от первоначального дома метода в мире MIME, где длинные строки были нарушением протокола. Пакет mail в Ruby на него намеренно опирается - в его Base64-кодировщике даже есть комментарий о том, что переносы строк Ruby удерживают вывод в пределах лимитов длины строк SMTP. Если вы кодируете тела писем, encode64 делает вам одолжение.

Но в любом другом контексте перенос - это налог. Самая частая авария - JSON-документ, где значение Base64 вдруг протягивается на две строки:

payload = { "logo" => Base64.encode64(File.binread("logo.png")) }
puts payload.to_json
# значение logo несёт переводы строк, которых никто не просил

А младший близнец той же ошибки - конечный перевод строки у коротких строк: Base64.encode64("s") возвращает "cw==\n", так что токен, который вы вставите в URL или сравните с ожидаемым значением, не сработает по причинам, за которыми вы будете гоняться двадцать минут. Лечение - strip, - но лучшее лечение - strict_encode64, который никогда не добавляет ни одного символа, которого вы не заработали. А ещё есть одна очаровательная асимметрия, о которой стоит знать: пустой вход выдаёт пустую строку без конечного перевода строки, так что Base64.encode64("") - это просто "".

urlsafe_encode64: алфавит, безопасный для ссылок

Два символа в стандартном алфавите создают проблемы везде, где присматривает парсер URL: + (пробел в строках запроса) и / (разделитель пути). RFC 4648 решил это заменой: - занимает место +, _ занимает место /, - а Ruby реализует это в Base64.urlsafe_encode64:

Base64.urlsafe_encode64("\xfb\xef\xbe".b)
# => "----"
Base64.urlsafe_encode64("\xff\xff\xff".b)
# => "____"

Эти два примера - алфавит в действии: те же байты, которые стандартный кодировщик отрисовывает как ++++ или ////, здесь выходят как ---- и ____, - символы, которые переживают URL, пути, имена файлов и поля форм без всякого процентного кодирования. Вывод - одна строка, как у strict_encode64.

Единственная опция метода - ключевое слово padding:, добавленное в Ruby 2.3, и именно его нужно знать. Спецификация JSON Web Token требует base64url без заполнения, как и многие другие схемы токенов:

Base64.urlsafe_encode64("*")
# => "Kg=="
Base64.urlsafe_encode64("*", padding: false)
# => "Kg"

Когда заполнение выключено, арифметика длины сдвигается: 3n + 1 байт теперь даёт 4n + 2 символа, а 3n + 2 байта дают 4n + 3. Сторона декодирования справляется - urlsafe_decode64 в Ruby само добавляет недостающее заполнение, - так что выдавать вывод без заполнения безопасно, но вывод с заполнением - более дружелюбный дефолт, когда другая сторона - строгий читатель RFC 2045. Одно предупреждение: выключайте заполнение только тогда, когда этого требует спецификация. Оно экономит один-два символа, а взамен покупает вам целый класс жалоб со стороны декодеров.

Что Ruby на самом деле кодирует: строки - это байты

Прежде чем переходить к сценариям, один специфичный для Ruby факт, который определяет всё: строка в Ruby - это последовательность байтов в ярлыке кодировки, и кодировщики смотрят только на байты. Ярлык говорит Ruby, как отображать и сравнивать строку; он не меняет то, что будет закодировано:

require "base64"
s = "h\u{e9}llo"
puts s.encoding
# => UTF-8
puts s.bytes.length
# => 6   e с акцентом занимает два байта
Base64.strict_encode64(s)
# => "aMOpbGxv"

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

broken = "h\u{e9}llo".b.force_encoding("UTF-8")
broken.setbyte(1, 0xFF)
puts broken.valid_encoding?
# => false
Base64.strict_encode64(broken)
# => какой-то base64, без ошибки, байты есть байты

Для настоящего двоичного пропустите всю текстовую механику и стройте байты через pack или читайте их через File.binread. Удовлетворительный пример - сигнатура PNG: те самые восемь байтов, с которых начинается каждый PNG-файл на Земле:

png_magic = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A].pack("C*")
Base64.strict_encode64(png_magic)
# => "iVBORw0KGgo="

JWT: подпись данных, которые при этом читаются

JSON Web Token - самый громкий потребитель URL-безопасного кодировщика Ruby. Токен - это три base64url-сегмента, соединённые точками: заголовок, пелод, подпись, - и спецификация говорит прямо: алфавит должен быть URL-безопасным, а заполнение должно быть выключено. Пакет jwt берёт на себя всё:

# В Gemfile: gem "jwt"
require "jwt"
token = JWT.encode(
  { sub: "1234567890", name: "Alice", exp: Time.now.to_i + 3600 },
  "my-secret-key",
  "HS256"
)
puts token
# => eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIi...
payload, header = JWT.decode(token, "my-secret-key", true, algorithm: "HS256")
puts header
# => {"alg"=>"HS256"}

Можно также посмотреть, как слой Base64 делает своё дело внутри токена, потому что сегменты - это просто base64url от JSON:

require "base64"
require "json"
payload_json = JSON.generate({ "sub" => "1234567890", "name" => "Alice" })
segment = Base64.urlsafe_encode64(payload_json, padding: false)
puts segment
# => eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0

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

HTTP Basic auth: собираем заголовок

Самый старый способ сказать «кто я» в HTTP до сих пор самый простой: закодируйте учётные данные в Base64, поставьте их после слова Basic и отправьте заголовок. Собрать его в Ruby - одна строка:

require "base64"
credentials = Base64.strict_encode64("alice:s3cr3t!")
puts "Basic #{credentials}"
# => Basic YWxpY2U6czNjcjN0IQ==

Стандартная библиотека Ruby делает ровно это за вас в Net::HTTP, вызывая ядровой шаблон pack напрямую: ["user:pass"].pack("m0") - то, к чему basic_auth сводится под капотом:

require "net/http"
request = Net::HTTP::Get.new("https://example.org/api")
request.basic_auth("alice", "s3cr3t!")
puts request["Authorization"]
# => Basic YWxpY2U6czNjcjN0IQ==

И предостережение о безопасности, озвученное один раз, чтобы оно осталось в протоколе: Base64 - переводчик, а не замок. Учётные данные в заголовке Basic auth читает любой, кто умеет читать пакеты. Этот заголовок приемлем только поверх HTTPS, где транспорт занимается настоящей защитой.

Data URI: встроенные изображения и шрифты

Data URI - это ответ веба на «хочу это изображение без отдельного файла»: тип медиа, слово base64, запятая и байты. Так однофайловые HTML-демо везут свои логотипы, так фавиконки прячутся внутри CSS, и так сгенерированное изображение может жить целиком в шаблонной строке:

require "base64"
png = File.binread("logo.png")
data_uri = "data:image/png;base64,#{Base64.strict_encode64(png)}"
css = "background-image: url(#{data_uri});"
puts css.length
# => ваш файл стилей, минус один HTTP-запрос

Здесь используйте strict_encode64 - пелод - это одна чистая строка, без переносов и без перевода строки. И следите за размером: встраиваемое изображение растёт примерно на треть, поэтому data URI блещут на мелких ассетах (фавиконки, логотипы, шрифты иконок) и раздуваются на крупных. Фото обложки в 2 мегабайта превращается в 2.7 мегабайта вашего HTML-документа, и пользователи это почувствуют при первом скролле на 4G.

Электронная почта: откуда Base64

Каждый другой сценарий в этой статье - потомок этого. SMTP проектировали в 1980-х под короткие строки семибитного текста - то есть в нём не мог поместиться JPEG. Исправление - Privacy-Enhanced Mail, затем MIME в 1993 году, - заключалось в переписывании двоичных данных в текст алфавитом из 64 символов, и это ровно тот формат, которым вы пользуетесь сегодня. Шрамы всё ещё видны в выводе Ruby: encode64 переносит на 60 символов - ширина, которая не служит какому-либо конкретному протоколу, как вы увидите позже, но достаточно короткая, чтобы держать почту вежливой.

На практике MIME-работу вы отдадите пакету mail. Прикрепите двоичный файл - пакет выберет Base64-кодировщик, перенесёт строки и напишет заголовки:

# В Gemfile: gem "mail"
require "mail"
message = Mail.new do |m|
  m.from = "dev@example.org"
  m.to = "ops@example.org"
  m.subject = "Binary report"
  m.add_file("report.bin")
end
puts message.encoded
# вложение несёт Content-Transfer-Encoding: base64

Не-ASCII текст в заголовках получает то же лечение в немного другом костюме: кодированные слова RFC 2047, которые оборачивают Base64 в метку кодировки между вопросительными знаками, вроде =?UTF-8?B?w7wgc2VjcmV0cw==?=. Если вы когда-нибудь будете собирать или разбирать их вручную, Base64 внутри - обычный, декодируется через decode64, а потом снова получает ярлык той кодировки, которую слово декларирует.

PEM-броня для ключей и сертификатов

Ключи и сертификаты носят PEM-броню, а броня - это Base64 с рамой: строка BEGIN, закодированные байты строками по 64 символа и строка END. Если вам когда-нибудь нужно будет создать PEM-файл из сырых DER-байтов, конструкция - двухшаговая обёртка:

require "base64"
der_bytes = File.binread("server.der")
body_lines = Base64.strict_encode64(der_bytes).scan(/.{1,64}/)
pem = (["-----BEGIN PRIVATE KEY-----"] + body_lines +
  ["-----END PRIVATE KEY-----"]).join("\n") + "\n"
File.write("server.key", pem)

Два замечания. Первое: на практике это вам почти никогда не понадобится, потому что пакет openssl пишет PEM за вас (key.to_pem), и метка между строками BEGIN и END должна совпадать с тем, что внутри: ошибётесь - получите файл, который отвергнет каждый инструмент в интернете. Второе: длина строк здесь - 64, классическая ширина PEM; encode64 в Ruby переносит на 60, и любой приличный PEM-парсер в принципе игнорирует длину строк, так что обе ширины декодируются без проблем.

Файлы: конвенция .b64

Самый распространённый формат файлов в мире Base64 - обычный текстовый файл с расширением .b64 (или .base64), содержащий один закодированный пелод, - подумайте об этом как о «файле, но безопасном для вставки куда угодно». Создать такой из Ruby - однострочник:

require "base64"
File.write("payload.b64", Base64.strict_encode64(File.binread("payload.bin")))
puts File.size("payload.b64")
# => примерно в 1.33 раза больше исходного размера

Используйте strict_encode64, чтобы в файле лежала одна чистая строка, - та конвенция, которой ожидают большинство инструментов декодирования (и строгий декодер Ruby). Прочитать обратно - зеркальное действие: прочитайте, декодируйте и запишите байты в двоичном режиме, чтобы ничего их не изменило по дороге:

encoded = File.read("payload.b64")
bytes = Base64.strict_decode64(encoded)
File.binwrite("restored.bin", bytes)

Если ваши файлы .b64 пришли от инструментов, которые переносят строки - некоторые варианты CLI base64 это делают, - удалите переводы строк перед строгим декодированием или используйте снисходительный декодер, который пропустит их бесплатно.

Конфигурационные файлы, переменные окружения и базы данных

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

Переменные окружения и файлы .env не могут держать сырые байты, поэтому байты кодируются до того, как покинут машину, на которой они находятся:

require "base64"
# где-то при развёртывании приложения
ENV["APP_LOGO"] = Base64.strict_encode64(File.binread("logo.png"))
# где-то при старте приложения
b64 = ENV.fetch("APP_LOGO")
File.binwrite("logo.png", Base64.decode64(b64))

В YAML есть нативный двоичный тип, и Psych занимается Base64 за вас: BINARY-строка, выгруженная в YAML, выходит как скаляр !binary, и загружается обратно побайтово идентичной:

require "yaml"
yaml_text = YAML.dump({ "logo" => File.binread("logo.png") })
puts yaml_text.lines.first(2)
# => "---"
# => "logo: !binary |-"
data = YAML.load(yaml_text)
puts data["logo"].encoding
# => ASCII-8BIT

В базах данных вопрос не в кодировании, а в типе хранилища. Если в вашей базе есть настоящая двоичная колонка - BLOB, BYTEA, VARBINARY, - используйте её и пусть драйвер носит байты. Base64 в TEXT-колонке - это паттерн для случаев, когда слой хранения понимает только строки: некоторые документные хранилища, JSON-подобные API или устаревшая схема, которую нельзя менять. Цена - налог в треть на размер колонки и дисциплина кодировать на входе и декодировать на выходе на каждом рубеже, без исключений.

Контрольные суммы, путешествующие текстом

Хеши - двоичные, но контрольные суммы в основном путешествуют текстом: списки целостности файлов, ключи кэша, отпечатки, строки логов. У каждого digest-класса в Ruby есть метод base64digest, который делает кодирование одним вызовом:

require "digest"
Digest::SHA256.base64digest("hello")
# => "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="

Вывод - стандартный Base64 с заполнением: ровно то, что получилось бы от Base64.strict_encode64(Digest::SHA256.digest("hello")), - так что его можно безопасно хранить, сравнивать и вставлять. Единственное решение - о согласованности: список контрольных сумм, сгенерированный с Base64, нужно сверять с Base64-выводом, а hex- и Base64-представления одного хеша - это разные строки, так что выберите одно и придерживайтесь его.

Кодирование больших вещей маленькими кусками

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

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

require "base64"
require "securerandom"
bin = SecureRandom.random_bytes(10_001)
whole = Base64.strict_encode64(bin)
chunked = bin.scan(/.{1,3}/m).map { |slice| Base64.strict_encode64(slice) }.join
puts chunked == whole
# => true

Тот же трюк даёт самодельный перенос строк, точно совпадающий с encode64: 45 байтов всегда кодируются ровно в 60 символов, так что нарезка входа на 45 байтов и склейка кусков переводами строк воспроизводит классический MIME-вывод по одной строке за раз, с одним куском в памяти за один заход:

def wrap_like_encode64(bin)
  lines = bin.scan(/.{1,45}/m).map { |slice| Base64.strict_encode64(slice) }
  lines.join("\n") + "\n"
end
bin = SecureRandom.random_bytes(10_001)
puts wrap_like_encode64(bin) == Base64.encode64(bin)
# => true

Из командной строки

Для кодирования тоже не нужен файл со скриптом. Однострочник читает файл и записывает его Base64 в stdout:

ruby -rbase64 -e 'print Base64.strict_encode64(File.binread(ARGV[0]))' payload.bin > payload.b64

А конвейерная форма читает stdin: именно так вы оберёте поток байтов от любой другой команды:

some_command | ruby -rbase64 -e 'print Base64.strict_encode64(STDIN.read)'

Держите print и в том, и в другом: лишний puts допишет перевод строки к вашему Base64, и для вывода strict_encode64 это превратит чистый токен в сломанный. То же жизненное правило, что и на стороне декодирования: если следующий потребитель вашего вывода строгий, вместе с ним может ехать только сам Base64.

Ловушки, за которые Ruby-разработчики платят лишними байтами

  • Конечный перевод строки в JSON. Base64.encode64 заканчивает каждый непустой результат переводом строки, так что значение, которое должно быть чистым токеном, приезжает в ваш JSON с сюрпризом \n в конце. Для всего, что будет храниться, сравниваться или отправляться в одной строке, используйте strict_encode64.
  • Перенос по 60 символов в токенах и URL. Тот же метод переносит длинный вывод на несколько строк. Перенесённая строка в URL - это два URL, а перенесённый токен - сломанный. Снова: strict_encode64, или strip/delete переводов строк, если вы застряли с выводом encode64.
  • Плюс и слэш в URL. Стандартный Base64 в строке запроса значит процентное кодирование %2B, %2F и %3D на выходе и надежду, что другая сторона их декодирует. urlsafe_encode64 убирает проблему у источника.
  • Заполнение не в том месте. JWT и другие схемы токенов хотят заполнение выключенным; MIME-читатели могут не справиться с отсутствующим заполнением. Выдавайте padding: false только там, где этого требует спецификация, и знайте, по какую сторону того забора сидит каждый из ваших потребителей.
  • Символы - не байты. Пятисимвольная строка с одной буквой с акцентом - это шесть байтов в UTF-8, а арифметика длины вывода идёт по байтам. Когда закодированный результат «слишком длинный», считайте байты, а не символы.
  • Налог в треть при проектировании схемы. BLOB в 16 КБ становится строкой Base64 примерно в 22 КБ в TEXT-колонке. Размеруйте колонки, кэши и пелоды API под закодированную форму, а не под двоичную.
  • Два алфавита - две разные строки. Те же байты кодируются по-разному в стандартном и URL-безопасном алфавитах, так что закодированное значение можно сравнивать только с другим значением из того же алфавита. Никогда не сравнивайте и не смешивайте их.
  • Base64 - не замок. Кодирование секрета не делает его секретным. У любого, у кого есть строка, - ваши данные; Base64 управляет только тем, как выглядят байты, а не тем, кто может их читать.

Привычки, которые экономят байты и баги

  • Сделайте strict_encode64 своим дефолтом. Переключайтесь на urlsafe_encode64 в ту же секунду, как вывод будет жить в URL, cookie или идентификаторе, и на encode64 - только когда пункт назначения - ориентированный на строки текстовый протокол вроде почты.
  • Держите алфавит согласованным между кодировщиком и декодером на обоих концах провода. Самый частый баг «Base64 сломан» - это производитель со стандартным алфавитом, встретивший URL-безопасного потребителя, или наоборот.
  • Кормите кодировщики теми байтами, которые вы имеете в виду: File.binread для файлов, pack для собранного вручную двоичного и UTF-8-строка, когда сама строка - данные. Кодировщик не станет оспаривать ваши решения - он просто считает байты.
  • Закладывайте рост. Всякий раз, когда строка Base64 пересечёт границу в контейнер с размером, умножайте на 4/3 и добавьте небольшой запас под заполнение.
  • Используйте Base64 для переносимости, никогда - для секретности. Если цель - удержать данные в тайне, инструмент - шифрование, а Base64 - только то, что вы делаете с шифротекстом потом.

Как Base64 стал Ruby-пакетом

Большую часть жизни модуль Base64 был просто файлом в стандартной библиотеке, как многие старейшие помощники Ruby. Строгие и URL-безопасные методы присоединились к оригинальной паре в линии разработки 1.9: вся библиотека base64, со всеми четырьмя методами, была добавлена в trunk в сентябре 2008 года и впервые вышла в 1.9.1 (2009), а ключевое слово padding: пришло с Ruby 2.3 в 2015 году. Всё, что вы видите в API сегодня, к тому моменту уже устоялось - остальная история о том, как модуль поставляется.

В 2020 году, с Ruby 3.0, команда ядра начала выносить стандартные библиотеки в собственные Ruby-пакеты, и base64 стал одним из них: версия 0.1.0, поддерживаемая в репозитории ruby/base64 ядровыми контрибьюторами. Он поставлялся как пакет по умолчанию - распространялся вместе с Ruby и был всегда доступен, так что require "base64" продолжал работать без каких-либо ритуалов. За Ruby 3.3 в 2023 году последовала версия 0.2.0, добавившая константу Base64::VERSION и заметно более богатый набор документации.

Затем в декабре 2024 года Ruby 3.4 перерисовала границу: base64 переехал из списка пакетов по умолчанию в список поставляемых пакетов, на ту же полку, что csv и drb. Поставляемые пакеты всё ещё идут вместе с языком, но от проектов на Bundler ожидается, что они их декларируют, так что если вы на Ruby 3.4 или новее, и ваше приложение управляется Bundler, добавьте gem "base64" в Gemfile (или выполните gem install base64) - и вы застрахованы. Ruby 4.0 в 2025 году принесла версию 0.3.0 с RBS-подписями типов, чтобы статические чекеры могли видеть модуль как следует.

На протяжении всего пути реализация оставалась прежней: несколько десятков строк чистого Ruby, обёрнутых вокруг ядровых шаблонов pack и unpack. Ни C-расширения, ни зависимостей, и - при сотнях миллионов загрузок на rubygems.org - один из самых устанавливаемых Ruby-пакетов на платформе.

Весёлые Ruby-факты

  • Весь модуль, кодировщики включительно, короткий настолько, что его можно прочесть за один кофе-брейк. encode64 - буквально [bin].pack("m"), strict_encode64 - [bin].pack("m0"), а urlsafe_encode64 - строгий кодировщик с двухбуквенной заменой сверху, минус заполнение, когда вы просите.
  • Перенос по 60 символов у encode64 не совпадает ни с 76-символьным максимумом MIME, ни с классическими 64 у PEM. Это просто то, что pack-шаблон m всегда делал, и Base64-кодировщик пакета mail одобряюще комментирует это: автоматический перенос строк Ruby удерживает вывод в пределах лимитов SMTP.
  • Net::HTTP из Ruby не утруждает себя модулем Base64 для Basic auth - он вызывает шаблон pack напрямую, что приятное напоминание о том, что модуль - это слой удобства над ядром, а не наоборот.
  • У каждого digest-класса есть метод base64digest, так что Digest::SHA256.base64digest - полноправный житель рядом с hexdigest: контрольные суммы в тексте без второго вызова.
  • Тег !binary в YAML - Base64 в маскировке. Psych делает кодирование в ту же секунду, как вы выгружаете BINARY-строку, поэтому конфигурационные файлы, полные двоичного, выглядят так, как выглядят.
  • Модуль, которым вы пользуетесь, не всегда был тем модулем, который вы помните. В старом Ruby были b64encode (перенос по заданной ширине) и decode_b (декодирование заголовков RFC 2047); оба исчезли в линии 1.9, так что любой код до 2010 года, который вы унаследуете и в котором они вызываются, умирает с NoMethodError.
  • Идентификаторы видео YouTube - это base64url без заполнения: одиннадцать символов, без плюса, без слэша, без равенства, - ровно тот короткий, безопасный для ссылок идентификатор, ради которого и был задуман URL-безопасный алфавит.

Обратная сторона

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

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

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