Codificación Base64 en Ruby: una guía completa
Tus datos tienen un destino que no acepta la forma en que se ven. Una imagen que tiene que vivir dentro de un documento JSON. Un token que tiene que cruzar una URL. Un adjunto que tiene que sobrevivir a un protocolo diseñado para texto de siete bits. Un secreto que tiene que estar en una variable de entorno sin romper las comillas. En cada uno de estos lugares, algo entre aquí y allí está a punto de destruir tu binario - y la solución tiene nombre: Base64.
En Ruby, el trabajo entero vive en un módulo que se distribuye con el lenguaje. Tres codificadores, nada que instalar, y una salida que puedes predecir hasta el carácter exacto antes de ejecutar el código. Esa previsibilidad es la mitad de la historia que la mayoría de las guías saltan, porque el encoding es donde las sorpresas cobran su peaje: un salto de línea final se cuela en tu JSON, un salto de línea que no pediste divide un token, y una elección equivocada de alfabeto arruina una URL. Esta guía recorre los tres codificadores, la matemática de la salida y cada payload que un desarrollador Ruby codifica de verdad, para que las sorpresas dejen de serlo.
Un repaso rápido antes de empezar: Base64 reescribe los datos tres bytes a la vez, emitiendo cuatro caracteres de un alfabeto de 64 símbolos, con uno o dos caracteres = de padding cuando la entrada no se divide exactamente entre tres - que es también por lo que la salida acaba siendo aproximadamente un tercio más grande que la entrada. La página de inicio de este sitio cubre el formato a fondo, así que este artículo deja la charla de formato para una respiración y se va directo al grano.
¿Qué codificador necesitas?
Ruby te da tres codificadores, y la elección entre ellos es un cuestionario de tres preguntas: ¿puede la salida contener saltos de línea? ¿Puede contener + o /? ¿Puede contener padding? Aquí está el plantel completo:
| Codificador | Forma de la salida | Saltos de línea | Padding | Acude a él cuando |
|---|---|---|---|---|
Base64.strict_encode64(bin) |
una línea, alfabeto estándar | nunca | siempre presente | JSON, tokens, APIs, archivos - el valor por defecto seguro |
Base64.encode64(bin) |
varias líneas, alfabeto estándar | después de cada 60 caracteres, más uno al final | siempre presente | cuerpos de correo y otros protocolos de texto orientados a líneas |
Base64.urlsafe_encode64(bin, padding: true) |
una línea, alfabeto de guion y guion bajo | nunca | a tu elección, activado por defecto | lo que caiga en una URL, cookie o identificador |
Si estás decidiendo bajo presión de tiempo, la respuesta corta es: strict_encode64 por defecto, urlsafe_encode64 cuando el resultado viajará dentro de una URL, y encode64 solo cuando el lado receptor sea un protocolo de texto que quiera líneas cortas. Todo lo de abajo explica por qué, y dónde cada elección te cobra en silencio.
strict_encode64: el caballo de trabajo
Base64.strict_encode64 es el codificador que usarás de verdad en la gran mayoría de tu código. Produce exactamente una línea de salida, siempre con el padding correcto, del alfabeto estándar:
require "base64"
Base64.strict_encode64("hello world")
# => "aGVsbG8gd29ybGQ="
Base64.strict_encode64("s")
# => "cw=="
Y como el algoritmo es determinista, puedes predecir la longitud exacta de la salida a partir de la entrada - sin adivinar, sin bugs de off-by-one en tus columnas de base de datos. La tabla de abajo es la aritmética completa:
| Longitud de entrada | Longitud de salida | Padding al final |
|---|---|---|
| 3n bytes (se divide exactamente) | 4n caracteres | ninguno |
| 3n + 1 bytes | 4n + 4 caracteres | dos = |
| 3n + 2 bytes | 4n + 4 caracteres | un = |
Así, 11 bytes se convierten en 16 caracteres, 100 bytes en 136, y un archivo de 1 megabyte en unos 1,33 megabytes de texto. Ese crecimiento de un tercio es el precio de admisión de cada payload Base64 que envías, y es el número que debes tener en la manga cada vez que una columna, una caché o un límite de tasa de una API empieza a apretar.
Base64.strict_encode64("123")
# => "MTIz" 3 bytes entran, 4 caracteres salen
Base64.strict_encode64("1234")
# => "MTIzNA==" 4 bytes entran, 8 caracteres salen, dos caracteres de padding
Base64.strict_encode64("12345")
# => "MTIzNDU=" 5 bytes entran, 8 caracteres salen, un carácter de padding
encode64: el que añade saltos de línea
Base64.encode64 es el clásico, y tiene un comportamiento que ha acabado con más de una tarde: dobla su salida. Cada 60 caracteres empieza una línea nueva, y siempre termina con un salto de línea final:
Base64.encode64("hello world")
# => "aGVsbG8gd29ybGQ=\n"
Base64.encode64("*" * 46)
# => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n"
El doblado no es un bug - es una característica heredada del hogar original del método en el mundo MIME, donde las líneas largas eran una violación del protocolo. La gem mail de Ruby se apoya en ella a propósito - su codificador Base64 incluso lleva un comentario al efecto de que el doblado de líneas de Ruby mantiene la salida dentro de los límites de longitud de línea de SMTP. Si estás codificando cuerpos de correo, encode64 te está haciendo un favor.
Pero en cualquier otro contexto, el doblado es un impuesto. El accidente más común es un documento JSON donde un valor Base64 de repente ocupa dos líneas:
payload = { "logo" => Base64.encode64(File.binread("logo.png")) }
puts payload.to_json
# el valor logo lleva saltos de línea que nadie pidió
Y el hermano menor del mismo bug es la newline final en cadenas cortas: Base64.encode64("s") devuelve "cw==\n", así que un token que pegas en una URL o comparas contra un valor esperado falla por razones que te llevarán veinte minutos de caza. La cura es un strip - pero la mejor cura es strict_encode64, que nunca añade un solo carácter que no te hayas ganado. También hay una asimetría encantadora que vale la pena conocer: una entrada vacía produce una cadena vacía sin newline final, así que Base64.encode64("") es solo "".
urlsafe_encode64: el alfabeto seguro para enlaces
Dos caracteres del alfabeto estándar causan problemas en cualquier sitio donde un parser de URLs esté mirando: + (un espacio, en las cadenas de consulta) y / (un separador de rutas). El RFC 4648 lo resolvió con un intercambio - - toma el lugar de +, _ toma el lugar de / - y Ruby lo implementa en Base64.urlsafe_encode64:
Base64.urlsafe_encode64("\xfb\xef\xbe".b)
# => "----"
Base64.urlsafe_encode64("\xff\xff\xff".b)
# => "____"
Esos dos ejemplos son el alfabeto en muestra: los mismos bytes que el codificador estándar renderiza como ++++ o //// salen como ---- y ____, caracteres que sobreviven a URLs, rutas, nombres de archivo y campos de formulario sin ningún percent-encoding. La salida es una línea, igual que en strict_encode64.
La única opción del método es la palabra clave padding:, añadida en Ruby 2.3, y es la que hay que conocer. La especificación de JSON Web Tokens exige base64url sin padding, y así lo hacen muchos otros esquemas de tokens:
Base64.urlsafe_encode64("*")
# => "Kg=="
Base64.urlsafe_encode64("*", padding: false)
# => "Kg"
Con el padding apagado, la matemática de longitud cambia: 3n + 1 bytes ahora rinden 4n + 2 caracteres y 3n + 2 bytes rinden 4n + 3. El lado del decodificador se las apaña - el urlsafe_decode64 de Ruby añade el padding que falta él solo - así que la salida sin padding es segura de emitir, pero la salida con padding es el valor por defecto más amable cuando el otro lado es un lector estricto del RFC 2045. Una precaución: apaga el padding solo cuando una especificación lo pida. Ahorra uno o dos caracteres y te compra una clase de quejas de decodificador.
Lo que Ruby codifica de verdad: las cadenas son bytes
Antes de los casos de uso, un hecho específico de Ruby que lo determina todo: una cadena de Ruby es una secuencia de bytes que lleva una etiqueta de encoding, y los codificadores solo miran los bytes. La etiqueta le dice a Ruby cómo mostrar y comparar la cadena; no cambia lo que se codifica:
require "base64"
s = "h\u{e9}llo"
puts s.encoding
# => UTF-8
puts s.bytes.length
# => 6 la e acentuada ocupa dos bytes
Base64.strict_encode64(s)
# => "aMOpbGxv"
Esa es la trampa detrás de "¿por qué mi salida es más larga de lo que esperaba?": la cadena que escribiste suele ser más corta en caracteres que en bytes, y Base64 cobra por byte. La dirección inversa es igual de silenciosa - una cadena UTF-8 inválida se codifica sin queja alguna, porque el codificador no tiene nada que validar:
broken = "h\u{e9}llo".b.force_encoding("UTF-8")
broken.setbyte(1, 0xFF)
puts broken.valid_encoding?
# => false
Base64.strict_encode64(broken)
# => algo de base64, sin error, los bytes son bytes
Para binario de verdad, salta toda la maquinaria de texto y construye bytes con pack o léelos con File.binread. Un ejemplo satisfactorio es la firma PNG - los ocho bytes que abren cada archivo PNG de la Tierra:
png_magic = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A].pack("C*")
Base64.strict_encode64(png_magic)
# => "iVBORw0KGgo="
JWTs: firmar datos que también son legibles
Los JSON Web Tokens son el consumidor más famoso del codificador URL-safe de Ruby. Un token es tres segmentos base64url unidos por puntos - cabecera, payload, firma - y la especificación es explícita: el alfabeto debe ser el URL-safe, y el padding debe estar apagado. La gem jwt se encarga de todo:
# Gemfile: gem "jwt"
require "jwt"
token = JWT.encode(
{ sub: "1234567890", name: "Alice", exp: Time.now.to_i + 3600 },
"my-secret-key",
"HS256"
)
puts token
# => eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIi...
payload, header = JWT.decode(token, "my-secret-key", true, algorithm: "HS256")
puts header
# => {"alg"=>"HS256"}
También puedes ver a la capa Base64 haciendo su trabajo dentro del token, porque los segmentos son solo base64url de JSON:
require "base64"
require "json"
payload_json = JSON.generate({ "sub" => "1234567890", "name" => "Alice" })
segment = Base64.urlsafe_encode64(payload_json, padding: false)
puts segment
# => eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0
Dos reglas van con este caso de uso. Nunca montes tu propio JWT a mano en producción - la firma es lo que hace que un token sea algo más que una confesión - y cuando decodifiques con la gem, fija el algoritmo en el hash de opciones como se muestra arriba, para que la propia cabecera del token no elija por ti el método de verificación.
Basic auth de HTTP: construyendo la cabecera
La forma más antigua de decir "quién soy" en HTTP sigue siendo la más simple: codifica las credenciales en Base64, ponlas después de la palabra Basic y envía la cabecera. En Ruby, construirla es una línea:
require "base64"
credentials = Base64.strict_encode64("alice:s3cr3t!")
puts "Basic #{credentials}"
# => Basic YWxpY2U6czNjcjN0IQ==
La librería estándar de Ruby hace exactamente esto por ti en Net::HTTP, llamando directamente a la plantilla pack del core - ["user:pass"].pack("m0") es a lo que se reduce basic_auth por dentro:
require "net/http"
request = Net::HTTP::Get.new("https://example.org/api")
request.basic_auth("alice", "s3cr3t!")
puts request["Authorization"]
# => Basic YWxpY2U6czNjcjN0IQ==
Y la salvedad de seguridad, dicha una vez para que quede constancia: Base64 es un traductor, no un candado. Las credenciales de una cabecera Basic auth las puede leer cualquiera que pueda leer el paquete. Esta cabecera solo es aceptable sobre HTTPS, donde el transporte es el que de verdad protege.
Data URIs: imágenes y fuentes en línea
Un data URI es la respuesta de la web al "quiero esta imagen sin archivo aparte": un tipo de medio, la palabra base64, una coma y los bytes. Así es como las demos HTML de archivo único incluyen sus logotipos, cómo los favicons se esconden dentro del CSS, y cómo una imagen generada puede vivir por completo en una cadena de plantilla:
require "base64"
png = File.binread("logo.png")
data_uri = "data:image/png;base64,#{Base64.strict_encode64(png)}"
css = "background-image: url(#{data_uri});"
puts css.length
# => tu hoja de estilos, menos una petición HTTP
Usa strict_encode64 aquí - el payload es una sola línea limpia, sin doblado, sin saltos de línea. Y cuida el tamaño: la imagen que incluyes en línea crece un tercio, así que los data URIs brillan con activos pequeños (favicons, logotipos, fuentes de iconos) e inflan con los grandes. Una foto de cabecera de dos megabytes se convierte en 2,7 megabytes de tu documento HTML, y tus usuarios lo sentirán en su primer scroll por 4G.
Correo: de dónde vino Base64
Todos los demás casos de uso de este artículo son descendientes de este. SMTP se diseñó en los años 80 para líneas cortas de texto de siete bits, o sea que no podía transportar un JPEG. La solución - Privacy-Enhanced Mail, y luego MIME en 1993 - fue reescribir los binarios como texto con un alfabeto de 64 símbolos, que es exactamente el formato que usas hoy. Las cicatrices aún se ven en la salida de Ruby: encode64 dobla a los 60 caracteres - un ancho que no sirve a ningún protocolo en particular, como verás más adelante, pero lo bastante corto para mantener el correo educado.
En la práctica dejarás que la gem mail haga el trabajo MIME. Adjunta un archivo binario y la gem elige el codificador Base64, dobla las líneas y escribe las cabeceras:
# Gemfile: gem "mail"
require "mail"
message = Mail.new do |m|
m.from = "dev@example.org"
m.to = "ops@example.org"
m.subject = "Binary report"
m.add_file("report.bin")
end
puts message.encoded
# la parte del adjunto lleva Content-Transfer-Encoding: base64
El texto no ASCII en cabeceras recibe el mismo tratamiento con un disfraz ligeramente distinto: las palabras codificadas RFC 2047 envuelven Base64 en una etiqueta de charset entre puntos de interrogación, como =?UTF-8?B?w7wgc2VjcmV0cw==?=. Si alguna vez construyes o analizas esas a mano, el Base64 de dentro es el corriente, decodificado con decode64 y luego reetiquetado con el charset que la palabra declara.
Armadura PEM para claves y certificados
Las claves y los certificados llevan armadura PEM, y la armadura es Base64 con un marco: una línea BEGIN, los bytes codificados en líneas de 64 caracteres y una línea END. Si alguna vez necesitas producir un archivo PEM a partir de bytes DER crudos, la construcción es un doblado en dos pasos:
require "base64"
der_bytes = File.binread("server.der")
body_lines = Base64.strict_encode64(der_bytes).scan(/.{1,64}/)
pem = (["-----BEGIN PRIVATE KEY-----"] + body_lines +
["-----END PRIVATE KEY-----"]).join("\n") + "\n"
File.write("server.key", pem)
Dos notas. Primera: casi nunca vas a necesitar esto, porque la gem openssl escribe PEM por ti (key.to_pem), y la etiqueta entre las líneas BEGIN y END tiene que coincidir con lo que hay dentro. Si te equivocas, produces un archivo que cada herramienta de internet rechaza. Segunda: la longitud de línea aquí es 64, el ancho clásico de PEM. El encode64 de Ruby dobla a 60, y cada parser PEM decente ignora las longitudes de línea por completo, así que cualquier ancho decodifica bien.
Archivos: la convención .b64
El formato de archivo más común en el mundo Base64 es un archivo de texto plano con extensión .b64 (o .base64) que contiene un solo payload codificado - piénsalo como "el archivo, pero seguro para pegar en cualquier parte". Producir uno desde Ruby es un one-liner:
require "base64"
File.write("payload.b64", Base64.strict_encode64(File.binread("payload.bin")))
puts File.size("payload.b64")
# => aproximadamente 1,33 veces el tamaño original
Usa strict_encode64 para que el archivo contenga una sola línea limpia - la convención que la mayoría de las herramientas de decodificación (y el decodificador estricto de Ruby) esperan. Leerlo de vuelta es la imagen en el espejo: lee, decodifica y escribe los bytes en modo binario para que nada los mute en el camino de salida:
encoded = File.read("payload.b64")
bytes = Base64.strict_decode64(encoded)
File.binwrite("restored.bin", bytes)
Si tus archivos .b64 vienen de herramientas que doblan líneas - algunas variantes del CLI base64 lo hacen - quita los saltos de línea antes de una decodificación estricta, o usa el decodificador indulgente, que los salta gratis.
Archivos de configuración, variables de entorno y bases de datos
Cada vez que los datos binarios tengan que vivir dentro de un documento de texto, Base64 es el puente. El patrón se repite en tres lugares con pequeñas variaciones.
Las variables de entorno y los archivos .env no pueden contener bytes crudos, así que los bytes se codifican antes de salir de la máquina que los tiene:
require "base64"
# en algún lugar donde aprovisionas la app
ENV["APP_LOGO"] = Base64.strict_encode64(File.binread("logo.png"))
# en algún lugar donde la app arranca
b64 = ENV.fetch("APP_LOGO")
File.binwrite("logo.png", Base64.decode64(b64))
YAML tiene un tipo binario nativo, y Psych se encarga del Base64 por ti - una cadena BINARY volcada a YAML sale como un escalar !binary, y carga de vuelta byte a byte idéntica:
require "yaml"
yaml_text = YAML.dump({ "logo" => File.binread("logo.png") })
puts yaml_text.lines.first(2)
# => "---"
# => "logo: !binary |-"
data = YAML.load(yaml_text)
puts data["logo"].encoding
# => ASCII-8BIT
En bases de datos la pregunta es de tipo de almacenamiento, no de encoding. Si tu base de datos tiene una columna binaria de verdad - BLOB, BYTEA, VARBINARY - úsala y deja que el driver transporte los bytes. Base64 en una columna TEXT es el patrón para cuando la capa de almacenamiento solo habla cadenas: algunos document stores, APIs con forma de JSON o un esquema legado que no puedes cambiar. El precio es el impuesto de un tercio de tamaño sobre la columna, y la disciplina de codificar al entrar y decodificar al salir en cada frontera, sin excepción.
Checksums que viajan como texto
Los hashes son binarios, pero los checksums viajan sobre todo en texto: listas de integridad de archivos, claves de caché, huellas, líneas de log. Las clases de digest de Ruby cada una tiene un método base64digest que hace el encoding en una llamada:
require "digest"
Digest::SHA256.base64digest("hello")
# => "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
La salida es Base64 estándar con padding - lo mismo que obtendrías de Base64.strict_encode64(Digest::SHA256.digest("hello")) - así que es seguro de almacenar, comparar y pegar. La única decisión es la consistencia: una lista de checksums generada con Base64 hay que comprobarla contra salidas Base64, y las representaciones en hex y en Base64 del mismo hash son cadenas distintas, así que elige una y apégate a ella.
Codificar cosas grandes en trozos pequeños
Como los decodificadores, los codificadores son de tipo buffer: leen toda la entrada y emiten toda la salida. No hay un codificador en streaming en la librería estándar, así que para payloads grandes el plan es la memoria, y hay una simetría agradable en la matemática. El encoding hace crecer tus datos en un tercio, así que la salida - no la entrada - es tu asignación más grande, y para un archivo de 1 gigabyte debes esperar unos 1,33 gigabytes de texto delante de ti.
Si es demasiado para sostenerlo a la vez, puedes codificar en trozos, porque el alfabeto Base64 se auto-sincroniza en los límites de tres bytes: codifica cada rebanada de 3 bytes por separado y la concatenación es idéntica a codificar el todo:
require "base64"
require "securerandom"
bin = SecureRandom.random_bytes(10_001)
whole = Base64.strict_encode64(bin)
chunked = bin.scan(/.{1,3}/m).map { |slice| Base64.strict_encode64(slice) }.join
puts chunked == whole
# => true
El mismo truco te da un envoltorio de líneas hecho a mano que coincide exactamente con encode64: 45 bytes siempre codifican a exactamente 60 caracteres, así que partir la entrada a 45 bytes y unir las piezas con saltos de línea reproduce la salida MIME clásica, una línea a la vez, con solo una rebanada en memoria a la vez:
def wrap_like_encode64(bin)
lines = bin.scan(/.{1,45}/m).map { |slice| Base64.strict_encode64(slice) }
lines.join("\n") + "\n"
end
bin = SecureRandom.random_bytes(10_001)
puts wrap_like_encode64(bin) == Base64.encode64(bin)
# => true
Desde la línea de comandos
El encoding tampoco necesita un archivo de script. La forma one-liner lee un archivo y escribe su Base64 en stdout:
ruby -rbase64 -e 'print Base64.strict_encode64(File.binread(ARGV[0]))' payload.bin > payload.b64
Y la forma pipe lee stdin, que es como envolverías un flujo de bytes de cualquier otro comando:
some_command | ruby -rbase64 -e 'print Base64.strict_encode64(STDIN.read)'
Mantén print en ambas - un puts de más añadiría un salto de línea a tu Base64, y para la salida de strict_encode64 eso convierte un token limpio en uno roto. La misma regla práctica que en el lado de decodificación: si el siguiente consumidor de tu salida es estricto, nada más que el Base64 en sí puede ir de viaje.
Las trampas que cuestan bytes de más a los desarrolladores Ruby
- El salto de línea final en JSON.
Base64.encode64termina cada resultado no vacío con un salto de línea, así que un valor que debería ser un token limpio llega a tu JSON con un\nsorpresa al final. Usastrict_encode64para todo lo que se almacenará, comparará o enviará en una sola línea. - El doblado de 60 caracteres en tokens y URLs. El mismo método dobla la salida larga en varias líneas. Una cadena doblada en una URL son dos URLs, y un token doblado es un token roto. De nuevo:
strict_encode64, ostrip/deletede los saltos de línea si te quedas atascado con salida deencode64. - Más y barra en URLs. Base64 estándar en una cadena de consulta significa percent-encodear
%2B,%2Fy%3Dal salir y rezar para que el otro lado los decodifique.urlsafe_encode64quita el problema en la fuente. - Padding en el sitio equivocado. Los JWTs y otros esquemas de tokens quieren el padding apagado; los lectores MIME pueden no aguantar el padding que falta. Emite
padding: falsesolo donde una especificación lo pida, y sabe en qué lado de esa valla se sienta cada uno de tus consumidores. - Los caracteres no son bytes. Una cadena de cinco caracteres con una letra acentuada son seis bytes en UTF-8, y la matemática de longitud de salida corre sobre bytes. Cuando el resultado codificado sale "demasiado largo", cuenta bytes, no caracteres.
- El impuesto de un tercio en el diseño de esquemas. Un BLOB de 16 KB se convierte en una cadena Base64 de unos 22 KB en una columna TEXT. Dimensiona tus columnas, cachés y payloads de API para la forma codificada, no para la forma binaria.
- Dos alfabetos, dos cadenas distintas. Los mismos bytes codifican distinto en los alfabetos estándar y URL-safe, así que un valor codificado solo es comparable contra otro valor del mismo alfabeto. Nunca los compares ni los mezcles.
- Base64 no es un candado. Codificar un secreto no lo hace secreto. Cualquiera con la cadena tiene tus datos; Base64 solo controla cómo se ven los bytes, no quién puede leerlos.
Hábitos que ahorran bytes y bugs
- Haz de
strict_encode64tu valor por defecto. Cambia aurlsafe_encode64en el momento en que la salida vivirá en una URL, cookie o identificador, y aencode64solo cuando el destino sea un protocolo de texto orientado a líneas como el correo. - Mantén el alfabeto consistente entre el codificador y el decodificador en ambos extremos del cable. El bug "Base64 roto" más común es un productor de alfabeto estándar encontrándose con un consumidor URL-safe, o viceversa.
- Alimenta a los codificadores con bytes que de verdad quieras codificar:
File.binreadpara archivos,packpara binario construido, y una cadena UTF-8 cuando la cadena es los datos. El codificador no cuestionará tus elecciones - solo cuenta bytes. - Pon presupuesto al crecimiento. Cada vez que una cadena Base64 cruza una frontera hacia un contenedor de tamaño fijo, multiplica por 4/3 y añade un poco de margen para el padding.
- Usa Base64 para portabilidad, nunca para secreto. Si la meta es mantener los datos privados, la herramienta es el cifrado, y Base64 es solo lo que haces con el texto cifrado después.
Cómo Base64 se convirtió en gem
Durante la mayor parte de su vida, el módulo Base64 fue solo un archivo en la librería estándar, al igual que muchos de los ayudantes más antiguos de Ruby. Los métodos estricto y URL-safe se unieron a la pareja original durante la línea de desarrollo 1.9 - la librería base64 completa, con sus cuatro métodos, se añadió al trunk en septiembre de 2008 y se distribuyó por primera vez en 1.9.1 (2009), y la palabra clave padding: llegó con Ruby 2.3 en 2015. Todo lo que hay sobre la API que ves hoy se había asentado para entonces - el resto de la historia es sobre cómo se distribuye el módulo.
En 2020, con Ruby 3.0, el equipo core empezó a extraer librerías estándar en sus propias gems, y base64 se convirtió en una de ellas: versión 0.1.0, mantenida en el repositorio ruby/base64 por los contribuyentes core. Se distribuyó como default gem - se reparte con Ruby y siempre está disponible, así que require "base64" siguió funcionando sin ceremonia alguna. La versión 0.2.0 siguió con Ruby 3.3 en 2023, añadiendo la constante Base64::VERSION y un conjunto de documentación mucho más rico.
Luego Ruby 3.4 en diciembre de 2024 trazó la línea de nuevo: base64 se movió de la lista de default gems a la lista de bundled gems, la misma estantería que csv y drb. Las bundled gems aún se distribuyen con el lenguaje, pero a los proyectos basados en Bundler se les espera que las declaren, así que si estás en Ruby 3.4 o posterior y tu app va con Bundler, añade gem "base64" a tu Gemfile (o ejecuta gem install base64) y estás cubierto. Ruby 4.0 en 2025 trajo la versión 0.3.0, con firmas de tipos RBS para que los chequeadores estáticos puedan ver el módulo como debe ser.
A lo largo de todo el viaje, la implementación siguió siendo lo que siempre fue: unas pocas docenas de líneas de Ruby puro envueltas alrededor de las plantillas pack y unpack del core. Sin extensión C, sin dependencias, y - con un contador de descargas en los cientos de millones en rubygems.org - una de las gems más instaladas de la plataforma.
Datos divertidos de Ruby
- El módulo entero, codificadores incluidos, es lo bastante corto para leerlo en un descanso de café.
encode64es literalmente[bin].pack("m"),strict_encode64es[bin].pack("m0"), yurlsafe_encode64es el codificador estricto con un intercambio de dos letras encima, menos el padding cuando lo pides. - El doblado de 60 caracteres de
encode64no coincide ni con el máximo de 76 caracteres de MIME ni con el clásico 64 de PEM. Simplemente es lo que la plantilla packmha hecho siempre, y el codificador Base64 de la gemmailcomenta al respecto con aprobación: el doblado automático de líneas de Ruby mantiene la salida dentro de los límites de SMTP. - El
Net::HTTPde Ruby ni se molesta con el móduloBase64para el Basic auth - llama a la plantillapackdirectamente, lo cual es un bonito recordatorio de que el módulo es una capa de conveniencia sobre el core, no al revés. - Cada clase de digest lleva un método
base64digest, así queDigest::SHA256.base64digestes un ciudadano de primera al lado dehexdigest- checksums en texto sin una segunda llamada. - La etiqueta
!binaryde YAML es Base64 disfrazado. Psych hace el encoding en el momento en que vuelcas una cadena BINARY, que es por lo que los archivos de configuración llenos de binarios se ven como se ven. - El módulo que estás usando no fue siempre el módulo que recuerdas. El Ruby viejo tenía
b64encode(doblado a un ancho elegido) ydecode_b(decodificación de cabeceras RFC 2047); ambos desaparecieron en la línea 1.9, así que cualquier código pre-2010 que heredes y que los llame muere con unNoMethodError. - Los IDs de vídeo de YouTube son base64url sin padding: once caracteres, sin el signo de suma, sin barra, sin igual - que es exactamente el tipo de identificador corto y seguro para enlaces para el que se diseñó el alfabeto URL-safe.
El lado opuesto
Ahora tienes el panorama completo del encoding: un caballo de trabajo por defecto que nunca te sorprende, un clásico que dobla líneas para los protocolos que lo exigen, un alfabeto seguro para enlaces con un interruptor de padding, y las reglas a nivel de byte que deciden exactamente cómo se verá tu salida. La dirección inversa - desmontar una cadena Base64, elegir entre los tres decodificadores de Ruby y convertir los bytes resultantes en algo que puedas usar - tiene sus propias trampas silenciosas, empezando por un decodificador que nunca dice no. Ese lado de la calle se cubre a fondo en el artículo de decodificación Base64, enlazado más abajo.
Última actualización: 2026-09-08
Artículo relacionado: Decodificación Base64 en Ruby: una guía completa