Codificar y decodificar una cadena
Usa los métodos strict_ salvo que quieras expresamente una salida 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
Las cadenas de Ruby llevan asociada una codificación, y las cadenas decodificadas siempre vuelven como BINARY (ASCII-8BIT), porque Base64 no sabe qué significan los bytes. force_encoding("UTF-8") les cambia la etiqueta sin copiarlas. Si te lo saltas, las comparaciones con literales UTF-8 fallan y los métodos de cadena tratan cada byte como un carácter.
Si la cadena de origen está en otra codificación (por ejemplo, Windows-1252 de un archivo antiguo), llama a .encode("UTF-8") antes de codificar a Base64, o el receptor verá bytes distintos de los que esperas.
encode64 frente a strict_encode64
encode64 es la variante MIME. Inserta un salto de línea cada 60 caracteres y siempre termina con uno:
Base64.encode64("hi") # => "aGk=\n"
Base64.encode64("x" * 60)
# => "eHh4...eHh4\neHh4eHh4eHh4eHh4eHh4\n" (60 chars per line)
Base64.strict_encode64("hi") # => "aGk="
Ese \n final es el error de Base64 más habitual en Ruby: rompe cabeceras HTTP, firmas y comparaciones de JSON. Fíjate en que Ruby corta a 60 caracteres, mientras que el RFC 2045 permite hasta 76 y la mayoría de los demás lenguajes usan 76. Las dos longitudes son válidas para el correo; si necesitas exactamente 76, el codificador Base64 MIME muestra ese formato.
Base64 URL-safe con y sin relleno
Base64.urlsafe_encode64("<<???>>") # => "PDw_Pz8-Pg=="
Base64.urlsafe_encode64("<<???>>", padding: false) # => "PDw_Pz8-Pg"
Base64.urlsafe_decode64("PDw_Pz8-Pg") # => "<<???>>"
urlsafe_decode64 acepta entradas con o sin relleno: añade los = que faltan y después decodifica en modo estricto. La palabra clave padding: existe desde Ruby 2.3. Para JWT y otros tokens, consulta Base64 frente a Base64URL o prueba el decodificador Base64URL.
Leer los claims de un JWT, sin verificarlo:
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"}
El formato de salida {"alg" => "HS256"} es el nuevo Hash#inspect de Ruby 3.4; las versiones anteriores imprimen {"alg"=>"HS256"}.
Codificar y decodificar un archivo
Lee y escribe en modo binario; si no, la traducción de saltos de línea de Windows y la conversión de codificación pueden cambiar los bytes:
puts Base64.strict_encode64(File.binread("hello.txt")) # aGVsbG8K
File.binwrite("copy.txt", Base64.strict_decode64("aGVsbG8K"))
Archivos grandes por bloques
El módulo Base64 no tiene una API de flujo, pero pack("m0") por bloques funciona siempre que cada bloque sea 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
Con un archivo de prueba de 300 KB, el resultado fue idéntico al de strict_encode64(File.binread(...)). Con cualquier otro tamaño de bloque aparece relleno = en mitad de la salida.
pack y unpack1 directamente
El módulo es una capa de comodidad. Esto es equivalente y no necesita 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)
Validación y errores
strict_decode64 lanza ArgumentError con el mensaje invalid base64 ante cualquier cosa que no sea Base64 canónico con relleno. Probamos seis entradas y todas lo lanzaron:
| Entrada | Problema |
|---|---|
PDw_Pz8-Pg== | caracteres URL-safe |
aGk | falta el relleno |
aGk=!! | basura después del relleno |
a | longitud imposible |
aGl= | bits de relleno distintos de cero |
"aGVs\nbG8K" | salto de línea |
def base64?(s)
Base64.strict_decode64(s)
true
rescue ArgumentError
false
end
El decode64 no estricto nunca lanza errores. Se salta todo lo que no entiende, lo que produce basura en lugar de un error:
Base64.decode64("aGk=!!") # => "hi"
Base64.decode64("PDw_Pz8-Pg==") # => "<<\x0F\xCF\xC3\xE0" (should be "<<???>>")
Usa decode64 solo con cuerpos MIME que tengan saltos de línea, y solo si confías en la fuente. Cuando una cadena se niega a decodificarse, el validador Base64 señala el problema y errores habituales de Base64 explica las causas habituales.
Ruby 3.4 y Bundler
Ruby 3.4 pasó base64 de las gemas por defecto a las gemas incluidas (bundled). Fuera de Bundler no cambia nada. Con bundle exec, un Gemfile que no la incluya falla:
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)
Añade gem "base64" al Gemfile o al gemspec, o sustituye las llamadas por pack("m0") / unpack1("m0"), que forman parte del núcleo de Ruby.
Preguntas frecuentes
¿Por qué Base64.encode64 añade \n a mi cadena?
encode64 genera una salida RFC 2045 con un salto de línea cada 60 caracteres y otro al final. Usa strict_encode64 para cabeceras, JSON y URL; nunca añade saltos de línea.
¿Por qué obtengo «cannot load such file -- base64» en Ruby 3.4?
Desde Ruby 3.4, base64 es una gema incluida (bundled) y no una gema por defecto. El ruby normal la sigue cargando, pero con Bundler tienes que añadir gem "base64" al Gemfile. O sáltate el require y usa [data].pack("m0").
¿Cómo decodifico Base64 sin relleno en Ruby?
Base64.urlsafe_decode64 añade por sí mismo el relleno que falta. Para el alfabeto estándar, añade "=" * (-s.length % 4) antes de llamar a strict_decode64.