Закодировать и декодировать строку
Используйте методы 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.