Codificar e decodificar uma string
Use os métodos strict_, a menos que você queira saída MIME de propósito:
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
As strings do Ruby carregam uma codificação, e strings decodificadas sempre voltam como BINARY (ASCII-8BIT), porque o Base64 não sabe o que os bytes significam. O force_encoding("UTF-8") muda o rótulo sem copiar. Se você pular esse passo, comparações com literais UTF-8 falham e os métodos de string tratam cada byte como um caractere.
Se a string de origem estiver em outra codificação (digamos, Windows-1252 vinda de um arquivo legado), chame .encode("UTF-8") antes de codificar em Base64, senão quem recebe verá bytes diferentes dos que você espera.
encode64 vs strict_encode64
O encode64 é a versão MIME. Ele insere uma quebra de linha a cada 60 caracteres e sempre termina com uma:
Base64.encode64("hi") # => "aGk=\n"
Base64.encode64("x" * 60)
# => "eHh4...eHh4\neHh4eHh4eHh4eHh4eHh4\n" (60 chars per line)
Base64.strict_encode64("hi") # => "aGk="
Esse \n no final é o bug de Base64 mais comum em Ruby: ele quebra headers HTTP, assinaturas e comparações de JSON. Repare que o Ruby quebra em 60 caracteres, enquanto a RFC 2045 permite até 76 e a maioria das outras linguagens usa 76. Os dois são válidos para e-mail; se você precisa de exatamente 76, o codificador Base64 MIME mostra esse formato.
Base64 URL-safe com e sem preenchimento
Base64.urlsafe_encode64("<<???>>") # => "PDw_Pz8-Pg=="
Base64.urlsafe_encode64("<<???>>", padding: false) # => "PDw_Pz8-Pg"
Base64.urlsafe_decode64("PDw_Pz8-Pg") # => "<<???>>"
O urlsafe_decode64 aceita entrada com ou sem preenchimento; ele acrescenta os = que faltam e então decodifica em modo estrito. A keyword padding: existe desde o Ruby 2.3. Para JWTs e outros tokens, veja Base64 vs Base64URL ou experimente o decodificador Base64URL.
Para ler as claims de um JWT, sem verificá-lo:
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"}
O formato de saída {"alg" => "HS256"} é o novo Hash#inspect do Ruby 3.4; versões anteriores imprimem {"alg"=>"HS256"}.
Codificar e decodificar um arquivo
Leia e escreva em modo binário, senão a tradução de quebras de linha do Windows e a conversão de codificação podem alterar os bytes:
puts Base64.strict_encode64(File.binread("hello.txt")) # aGVsbG8K
File.binwrite("copy.txt", Base64.strict_decode64("aGVsbG8K"))
Arquivos grandes em blocos
O módulo Base64 não tem API de streaming, mas pack("m0") em blocos funciona, desde que cada bloco tenha um múltiplo de 3 bytes:
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
Num arquivo de teste de 300 KB, o resultado foi idêntico ao de strict_encode64(File.binread(...)). Com qualquer outro tamanho de bloco, aparece preenchimento = no meio da saída.
pack e unpack1 diretamente
O módulo é uma camada de conveniência. Estas formas são equivalentes e dispensam o 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)
Validação e erros
O strict_decode64 lança ArgumentError com a mensagem invalid base64 para tudo o que não for Base64 canônico com preenchimento. Testamos seis entradas, e todas lançaram o erro:
| Entrada | Problema |
|---|---|
PDw_Pz8-Pg== | caracteres URL-safe |
aGk | falta de preenchimento |
aGk=!! | lixo depois do preenchimento |
a | comprimento impossível |
aGl= | bits de preenchimento diferentes de zero |
"aGVs\nbG8K" | quebra de linha |
def base64?(s)
Base64.strict_decode64(s)
true
rescue ArgumentError
false
end
O decode64 não estrito nunca lança erro. Ele pula tudo o que não entende, o que gera lixo em vez de um erro:
Base64.decode64("aGk=!!") # => "hi"
Base64.decode64("PDw_Pz8-Pg==") # => "<<\x0F\xCF\xC3\xE0" (should be "<<???>>")
Use o decode64 só para corpos MIME com quebras de linha, e só quando confiar na origem. Quando uma string se recusa a decodificar, o validador de Base64 aponta o problema, e erros comuns de Base64 explica as causas habituais.
Ruby 3.4 e Bundler
O Ruby 3.4 moveu o base64 das default gems para as bundled gems. Fora do Bundler, nada muda. Com bundle exec, um Gemfile sem ele falha:
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)
Acrescente gem "base64" ao Gemfile ou ao gemspec, ou troque as chamadas por pack("m0") / unpack1("m0"), que fazem parte do núcleo do Ruby.
Perguntas frequentes
Por que o Base64.encode64 acrescenta \n à minha string?
O encode64 gera saída no formato da RFC 2045, com uma quebra de linha a cada 60 caracteres e outra no final. Use strict_encode64 para headers, JSON e URLs; ele nunca acrescenta quebras de linha.
Por que aparece 'cannot load such file -- base64' no Ruby 3.4?
Desde o Ruby 3.4, o base64 é uma bundled gem, não mais uma default gem. O ruby puro ainda o carrega, mas com o Bundler você precisa acrescentar gem "base64" ao Gemfile. Ou dispense o require e use [data].pack("m0").
Como decodificar Base64 sem preenchimento em Ruby?
O Base64.urlsafe_decode64 acrescenta o preenchimento que falta sozinho. Para o alfabeto padrão, concatene "=" * (-s.length % 4) antes de chamar strict_decode64.