Загрузить файл
  1. base64hub
  2. Base64 в коде
  3. Ruby
Код

Base64 в Ruby

Модуль Base64 в Ruby – тонкая обёртка над Array#pack и String#unpack1. Главная ловушка в том, что нестрогие методы добавляют переносы строк. Результаты ниже получены на Ruby 3.4.

Закодировать и декодировать строку

Используйте методы strict_, если только вам не нужен именно вывод в стиле MIME:

require "base64"

enc = Base64.strict_encode64("héllo wörld")
puts enc          # aMOpbGxvIHfDtnJsZA==

dec = Base64.strict_decode64(enc).force_encoding("UTF-8")
puts dec          # héllo wörld

Строки в Ruby несут с собой кодировку, а декодированные строки всегда возвращаются как BINARY (ASCII-8BIT), потому что Base64 не знает, что означают байты. force_encoding("UTF-8") меняет метку без копирования. Если её пропустить, сравнение с литералами в UTF-8 не сработает, а строковые методы будут считать каждый байт отдельным символом.

Если исходная строка в другой кодировке (скажем, Windows-1252 из старого файла), вызовите .encode("UTF-8") перед кодированием в Base64, иначе получатель увидит не те байты, которых вы ждёте.

encode64 и strict_encode64

encode64 – вариант для MIME. Он вставляет перевод строки через каждые 60 символов и всегда заканчивает им вывод:

Base64.encode64("hi")          # => "aGk=\n"
Base64.encode64("x" * 60)
# => "eHh4...eHh4\neHh4eHh4eHh4eHh4eHh4\n"   (60 chars per line)
Base64.strict_encode64("hi")   # => "aGk="

Этот \n в конце – самая частая ошибка с Base64 в Ruby: он ломает HTTP-заголовки, подписи и сравнение JSON. Учтите, что Ruby переносит строки через 60 символов, тогда как RFC 2045 допускает до 76, и большинство других языков используют 76. Для почты годятся оба варианта; если нужно ровно 76, такой формат показывает кодировщик MIME Base64.

URL-safe Base64 с дополнением и без

Base64.urlsafe_encode64("<<???>>")                  # => "PDw_Pz8-Pg=="
Base64.urlsafe_encode64("<<???>>", padding: false)  # => "PDw_Pz8-Pg"
Base64.urlsafe_decode64("PDw_Pz8-Pg")               # => "<<???>>"

urlsafe_decode64 принимает строки и с дополнением, и без: он дописывает недостающие = и затем декодирует строго. Ключевой аргумент padding: есть начиная с Ruby 2.3. Про JWT и другие токены читайте статью Base64 и Base64URL или попробуйте декодер Base64URL.

Прочитать claims JWT без проверки подписи:

require "json"

jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig"
header, payload, _sig = jwt.split(".")
JSON.parse(Base64.urlsafe_decode64(header))    # => {"alg" => "HS256"}
JSON.parse(Base64.urlsafe_decode64(payload))   # => {"sub" => "1234"}

Формат вывода {"alg" => "HS256"} – это новый Hash#inspect из Ruby 3.4; старые версии печатают {"alg"=>"HS256"}.

Закодировать и декодировать файл

Читайте и пишите в двоичном режиме, иначе преобразование переводов строк в Windows и перекодировка могут изменить байты:

puts Base64.strict_encode64(File.binread("hello.txt"))   # aGVsbG8K
File.binwrite("copy.txt", Base64.strict_decode64("aGVsbG8K"))

Большие файлы по частям

Потокового API в модуле Base64 нет, но pack("m0") на частях работает, если каждая часть кратна 3 байтам:

File.open("big.bin", "rb") do |src|
  File.open("big.b64", "wb") do |dst|
    while (chunk = src.read(3 * 1024 * 64))   # 192 KiB, divisible by 3
      dst.write([chunk].pack("m0"))
    end
  end
end

На тестовом файле в 300 КБ результат совпал с strict_encode64(File.binread(...)). С любым другим размером частей дополнение = появится в середине вывода.

pack и unpack1 напрямую

Модуль – лишь слой для удобства. Эти вызовы равнозначны и не требуют require:

["hi"].pack("m0")    # => "aGk="    (same as strict_encode64)
["hi"].pack("m")     # => "aGk=\n"  (same as encode64)
"aGk=".unpack1("m0") # => "hi"      (same as strict_decode64)

Проверка и ошибки

strict_decode64 бросает ArgumentError с сообщением invalid base64 на любой строке, кроме канонического Base64 с дополнением. Мы попробовали шесть вариантов, и все вызвали эту ошибку:

ВходПроблема
PDw_Pz8-Pg==URL-safe символы
aGkнет дополнения
aGk=!!мусор после дополнения
aневозможная длина
aGl=ненулевые биты дополнения
"aGVs\nbG8K"перевод строки
def base64?(s)
  Base64.strict_decode64(s)
  true
rescue ArgumentError
  false
end

Нестрогий decode64 не бросает исключений никогда. Он пропускает всё, чего не понимает, и вместо ошибки выдаёт мусор:

Base64.decode64("aGk=!!")        # => "hi"
Base64.decode64("PDw_Pz8-Pg==")  # => "<<\x0F\xCF\xC3\xE0"   (should be "<<???>>")

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

Ruby 3.4 и Bundler

В Ruby 3.4 base64 переехал из default gems в bundled gems. Вне Bundler ничего не меняется. Под bundle exec проект, в Gemfile которого его нет, падает:

warning: base64 was loaded from the standard library, but is not part of the
default gems starting from Ruby 3.4.0.
...
cannot load such file -- base64 (LoadError)

Добавьте gem "base64" в Gemfile или gemspec либо замените вызовы на pack("m0") / unpack1("m0"), которые входят в ядро Ruby.

Вопросы и ответы

Почему Base64.encode64 добавляет \n к строке?

encode64 выдаёт результат по RFC 2045: перевод строки через каждые 60 символов и в конце. Для заголовков, JSON и URL используйте strict_encode64, который переводов строк не добавляет никогда.

Откуда ошибка «cannot load such file -- base64» в Ruby 3.4?

Начиная с Ruby 3.4, base64 – bundled gem, а не default gem. Обычный ruby по-прежнему его загружает, но под Bundler нужно добавить gem "base64" в Gemfile. Или обойдитесь без require и используйте [data].pack("m0").

Как в Ruby декодировать Base64 без дополнения?

Base64.urlsafe_decode64 сам добавляет недостающее дополнение. Для стандартного алфавита допишите "=" * (-s.length % 4) перед вызовом strict_decode64.

Читайте также