Skip to content
Base64 en producción — Cuándo ayuda y cuándo no

Codificación

Base64 en producción — Cuándo ayuda y cuándo no

Base64 es un encoding de transporte, no compresión ni cifrado. Cuándo compensa su 33% de overhead en sistemas reales, y cuándo es un code smell a quitar.

Base64 compensa en un puñado de situaciones específicas y es code smell en todas las demás. Los ingenieros tiran de él por costumbre al tocar algo binario, pagan el 33% de sobrecoste para siempre y añaden peaje de legibilidad. Esta guía es el árbol de decisión.

Qué es Base64 en realidad

Base64 mapea cada 3 bytes de entrada (24 bits) a 4 caracteres de salida (6 bits cada uno) tomados de un alfabeto de 64 caracteres: A-Z, a-z, 0-9, y dos símbolos. El Base64 estándar usa + y /; el Base64 URL-safe (RFC 4648 §5) usa - y _. El padding con = lleva la longitud de salida a múltiplo de 4.

Esa es la spec entera. No es compresión (salida 4/3 del input, +33% antes de padding). No es cifrado (el decode es público). Es un encoding de transporte: una forma de mover bytes por un canal que solo transporta texto con seguridad.

VarianteAlfabetoPaddingSe usa en
Base64 estándar (RFC 4648 §4)A-Z a-z 0-9 + /=MIME, PEM, la mayoría de APIs
URL-safe (RFC 4648 §5)A-Z a-z 0-9 - _a menudo omitidoJWT, tokens URL
Base64url sin paddingigual §5ningunoJWT base64url(payload)

La variante URL-safe existe porque + y / tienen significado en URLs (+ es espacio en form encoding, / es separador de path). Mira la guía de URL encoding para las reglas que lo hacen importante.

La tabla de costes

Antes de adoptar Base64 en cualquier sitio, cuéntalo:

  • Tamaño: ceil(n / 3) * 4 bytes por n bytes de entrada. Un archivo de 1 MB se convierte en 1,37 MB en la red. Gzip recupera casi todo para payloads tipo-texto, pero sigues quemando CPU en ambos extremos.
  • CPU: Despreciable en hardware moderno para blobs pequeños. Real para throughput de megabytes por request.
  • Legibilidad: Una línea de log con 4 KB de Base64 es una línea que no puedes grepear.
// Chequeo rápido de overhead
const bytes = new Uint8Array(1_000_000);
const b64 = btoa(String.fromCharCode(...bytes));
console.log(b64.length); // 1_333_336 — exactamente 4/3 + padding

Para experimentar con los pasos de encode y decode directamente, las herramientas text-to-binary y binary-to-text te permiten ver el layout de bytes detrás del telón.

Dónde Base64 se gana su sitio

Cuatro casos en los que Base64 es correcto:

  1. Embeber binarios pequeños dentro de documentos de texto. CSS url(data:image/png;base64,...) para favicons, firmas de email, PDFs con imágenes. Por debajo de 2-4 KB el round-trip HTTP extra compensa el 33% extra.
  2. Payloads JSON o XML que deben llevar bytes. JSON no tiene tipo binario. Base64 en un campo string es el escape canónico. Alternativas (multipart, upload URL separada) son mejores cuando el payload pasa de unos pocos KB.
  3. Claves y certificados criptográficos envueltos en PEM. -----BEGIN CERTIFICATE----- seguido de Base64 de bytes DER es el formato universal para material X.509 y PKCS.
  4. Payloads JWT. Header, claims y firma se encodean cada uno con base64url sin padding. No es negociable: la spec está escrita así.

Dónde Base64 hace daño

  • Guardar binarios en una base de datos relacional como columna TEXT. Tu BD ya tiene BYTEA, BLOB, VARBINARY. Usarlos ahorra 25%, preserva indexabilidad de hashes y evita una pasada encode/decode en cada lectura.
  • Base64 en URLs de tamaño sin acotar. Los navegadores topan las URLs entre 2000 y 8000 caracteres dependiendo del stack. Mete 10 KB en query string y lo tocas.
  • “Ofuscar” secretos en un archivo de config. Base64 no es un secreto. Cualquiera hace echo ZHJvd3NzYXA= | base64 -d. Si necesitas un secreto, usa un secret manager.
  • Serializar UUIDs para URLs. Un UUID ya son 36 caracteres hex. Base64url lo deja en 22 pero la pérdida de legibilidad casi nunca justifica el ahorro. Mira la pieza UUID v4 vs v7 para la discusión de primary keys.

Data URIs — el detalle que pilla a los equipos

Los data URIs embeben Base64 (o bytes percent-encoded) dentro de una URL: data:[<mediatype>][;base64],<data>. Tres reglas:

  • Chrome y Firefox ya no permiten data URIs para navegación top-level; eran vector común de phishing. Siguen bien para <img>, CSS url(), y <iframe> con los caveats de abajo.
  • Content Security Policy bloquea fuentes data: por defecto. Necesitas img-src data: o similar para reabrirlas.
  • Los navegadores móviles suelen cappar la longitud de data URIs en 2 MB, pero el rendimiento se derrumba mucho antes.
<!-- Bien: icono inline diminuto, menos de 1 KB -->
<img src="data:image/svg+xml;base64,PHN2Zy4uLg==" alt="">

<!-- Mal: inlining de imagen hero de 500 KB -->
<img src="data:image/png;base64,iVBORw0KGgo...">

URL-safe vs estándar — el bug que acabarás pisando

Trampa sutil: una librería que defaultea a Base64 estándar produce salida con + y /, que luego llega a un servidor que espera Base64 URL-safe. Dos fallos clásicos:

  • El carácter + llega decodificado como espacio porque el servidor hizo decodeURIComponent en el query string antes de decodificar Base64.
  • El padding = se percent-encodea a %3D, el decoder no lo strippea, y la validación de longitud falla.

Arreglo: elige una variante por interfaz y documéntala. Para cualquier cosa que toque URLs, JWTs o cookies, usa URL-safe sin padding y normaliza al recibir. Para MIME y PEM, quédate con el estándar.

Notas de rendimiento

Modelo mental aproximado de cuándo el coste de encode/decode importa en la práctica:

  • Bajo 10 KB: efectivamente gratis en cualquier CPU de la última década. No optimices.
  • 10 KB a 1 MB: microsegundos a milisegundos bajos por operación. Sigue despreciable para la mayoría de requests web.
  • 1 MB a 100 MB: ahora sí puedes medirlo. Un payload de 100 MB encodeado con Buffer.from(...).toString("base64") en Node cuesta decenas de milisegundos. Si lo haces en un hot path, considera streaming o evita el encode por completo.
  • Por encima de 100 MB: Base64 es casi siempre la respuesta equivocada. Usa un signed URL, un multipart upload, o un canal lateral.

Otra arista afilada que merece mención: atob y btoa en navegadores trabajan sobre binary strings (un carácter por byte), no sobre Uint8Array. Llamar btoa("é") lanza InvalidCharacterError porque é son dos bytes en UTF-8 pero btoa espera semántica Latin-1. El enfoque moderno correcto es Uint8Array.prototype.toBase64() (Baseline 2024) o, como fallback, encodear a través de un TextEncoder primero.

Conclusiones

Usa Base64 cuando estás metiendo bytes por un canal solo-texto (JSON, URLs, PEM, cuerpos de email). No lo uses como tipo de base de datos, como ofuscación, ni como martillo genérico “binario-a-string”. Para los primos del encoding que aparecen en las mismas conversaciones, mira la guía de character encoding y las reglas de URL encoding.

Herramientas relacionadas

Por ·