Enviar um arquivo
  1. base64hub
  2. Base64 no código
  3. Ruby
Código

Base64 em Ruby

O módulo Base64 do Ruby é uma camada fina sobre Array#pack e String#unpack1. A principal armadilha é que os métodos não estritos acrescentam quebras de linha. As saídas abaixo vêm do Ruby 3.4.

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:

EntradaProblema
PDw_Pz8-Pg==caracteres URL-safe
aGkfalta de preenchimento
aGk=!!lixo depois do preenchimento
acomprimento 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.

Continue lendo