Codificación Base64 explicada: cuándo, por qué y cómo usarla

9 min12 de mayo de 2026

Qué hace realmente la codificación Base64

La codificación Base64 convierte datos binarios arbitrarios en una cadena de 64 caracteres ASCII imprimibles. No es cifrado, no es compresión, no hay magia: es simplemente una transformación reversible de bytes a texto. El nombre proviene del alfabeto de 64 caracteres utilizados: A-Z, a-z, 0-9, más dos caracteres adicionales (+ y / en la variante estándar).

El algoritmo fue formalizado en el RFC 4648 (publicado en 2006, reemplazando al anterior RFC 3548). Existe porque muchos sistemas — protocolos de correo electrónico, payloads JSON, documentos XML, parámetros de URL — fueron diseñados para transportar texto, no bytes crudos. Cuando necesitas insertar una imagen PNG en un correo o incrustar un PDF en una respuesta de API JSON, Base64 te da una forma de representar ese bloque binario como texto plano. La contrapartida es el tamaño: cada 3 bytes de entrada se convierten en 4 caracteres de salida.

Un punto que muchos tutoriales pasan por alto: Base64 no es codificación en el mismo sentido que UTF-8 o ASCII. Esos mapean caracteres a bytes. Base64 mapea bytes a caracteres — va en la dirección opuesta. UTF-8 permite a los ordenadores almacenar texto. Base64 permite a sistemas basados en texto transportar datos binarios.

Cómo funciona Base64 paso a paso

El algoritmo procesa la entrada en bloques de 3 bytes (24 bits). Divide esos 24 bits en cuatro grupos de 6 bits. Cada valor de 6 bits (0-63) se mapea a un carácter del alfabeto Base64. Tres bytes de entrada producen cuatro caracteres de salida — por eso Base64 siempre incrementa el tamaño exactamente un 33% (más el padding).

Cuando la longitud de la entrada no es divisible por 3, entra en juego el padding. Un byte sobrante produce dos caracteres Base64 más "==". Dos bytes sobrantes producen tres caracteres Base64 más "=". El carácter de padding indica a los decodificadores cuántos bytes descartar al final. Algunas implementaciones (como base64url) eliminan el padding porque la longitud misma lo implica.

Veamos el proceso para codificar la cadena "Hi" (dos bytes: 0x48 0x69). En binario es 01001000 01101001. Necesitamos 24 bits, así que rellenamos con ceros: 01001000 01101001 00000000. Dividimos en grupos de 6 bits: 010010 000110 100100 000000. Mapeamos a índices: 18, 6, 36, 0. Buscamos en el alfabeto: S, G, k, A. Añadimos un "=" por el byte de relleno. Resultado: "SGk=".

El error más común al implementarlo manualmente es olvidar que los ceros de relleno forman parte del último grupo de 6 bits, no son separados. El decodificador necesita saber que la "A" final representa ceros de relleno, no un byte real con valor cero. Para eso sirve el "=".

// Codificando "Hi" paso a paso
const input = "Hi";
const bytes = [0x48, 0x69]; // H=72, i=105

// Paso 1: Convertir a binario (rellenar a 24 bits)
// 01001000 01101001 00000000
//
// Paso 2: Dividir en grupos de 6 bits
// 010010 | 000110 | 100100 | 000000
//   18       6       36       0
//
// Paso 3: Mapear al alfabeto Base64
//   S        G       k        A
//
// Paso 4: Añadir padding (1 byte fue rellenado)
// Resultado: "SGk="

console.log(btoa("Hi")); // "SGk="
console.log(atob("SGk=")); // "Hi"

// En Node.js:
Buffer.from("Hi").toString("base64"); // "SGk="
Buffer.from("SGk=", "base64").toString(); // "Hi"

El sobrecoste del 33% y por qué importa

Cada 3 bytes de entrada producen 4 bytes de salida. Es un incremento fijo del 33,3% antes de contar padding o saltos de línea. Para una imagen de 1 MB, la versión Base64 ocupa al menos 1,33 MB. Para una miniatura de vídeo de 10 MB incrustada en una respuesta JSON, estás enviando 13,3 MB por la red. Esto se acumula rápidamente.

Un caso real: una aplicación móvil consumía datos de forma excesiva. La API devolvía avatares de usuario como cadenas Base64 dentro de JSON. Cada avatar de 200 KB se convertía en 267 KB de texto Base64, y la respuesta JSON con 20 avatares pesaba 5,3 MB. Cambiar a URLs de imagen con CDN redujo el payload a 4 KB. La lección: que puedas incrustar binarios en JSON no significa que debas hacerlo.

Hay un segundo coste que suele pasar desapercibido: las cadenas Base64 no se pueden transmitir en streaming ni decodificar parcialmente. Con un archivo binario puedes empezar a renderizar los primeros bytes mientras el resto se descarga. Con Base64, normalmente necesitas la cadena completa antes de decodificar. Esto importa especialmente para payloads grandes en conexiones lentas.

Cuándo usar Base64: casos de uso reales

Adjuntos de email (MIME): Este es el caso de uso original. SMTP fue diseñado para texto ASCII de 7 bits. Los adjuntos binarios se codifican en Base64 para que sobrevivan el tránsito por servidores de correo que podrían eliminar el octavo bit. Cada adjunto que has enviado por email usa Base64 internamente.

Data URIs en HTML/CSS: Incrustar imágenes pequeñas directamente en el markup evita una petición HTTP adicional. Un icono de 2 KB como data URI (data:image/png;base64,...) ahorra un round trip. Pero cualquier cosa por encima de ~5 KB suele ser mejor servida como archivo separado — el sobrecoste de Base64 más la imposibilidad de cachearlo independientemente lo convierten en una pérdida neta.

Payloads JSON y XML: Cuando una API necesita incluir datos binarios (una imagen de firma, un archivo pequeño, una clave criptográfica) en un formato basado en texto, Base64 es el enfoque estándar. Las políticas de POST pre-firmadas de AWS S3 usan JSON codificado en Base64. Los JWT codifican su header y payload como Base64url.

Autenticación HTTP Basic: La cabecera Authorization envía credenciales como "usuario:contraseña" codificado en Base64. Esto NO es cifrado — cualquiera que intercepte la cabecera puede decodificarlo instantáneamente. Es solo codificación para asegurar que caracteres especiales en contraseñas no rompan el formato de cabeceras HTTP. Siempre usa HTTPS con Basic Auth.

Cuándo NO usar Base64: errores comunes

No uses Base64 para seguridad. He visto código en producción que "cifra" claves de API codificándolas en Base64. Esto proporciona cero seguridad. Ejecutar atob() en una cadena Base64 toma microsegundos. Si necesitas proteger datos, usa cifrado real (AES-256-GCM) o hashing (SHA-256). Base64 es codificación, no cifrado.

No incrustes archivos grandes en respuestas JSON. Si tu API devuelve imágenes, PDFs o vídeos, sírvalos como respuestas binarias separadas con cabeceras Content-Type apropiadas. Usa URLs apuntando a un CDN o almacenamiento de objetos. El sobrecoste del 33%, la imposibilidad de cachear independientemente y la presión de memoria por cadenas grandes desaconsejan Base64 inline para cualquier cosa de más de unos pocos KB.

No uses Base64 en URLs sin cambiar a base64url. El Base64 estándar usa + y / que tienen significado especial en URLs. La variante base64url (RFC 4648 §5) los reemplaza por - y _ y elimina el padding =. Los JWT usan base64url por esta razón. Si usas Base64 estándar en un parámetro de query sin aplicar URL-encoding primero, tus datos se corromperán.

No codifiques en Base64 datos que ya son texto. He revisado código que codifica JSON en Base64 antes de enviarlo en un campo JSON. Terminas con datos doblemente codificados, un 33% más grandes y más difíciles de depurar. Si los datos ya son texto válido (cadena UTF-8, JSON, XML), inclúyelos directamente.

Variantes: Base64 estándar, URL-safe y MIME

El RFC 4648 define varios alfabetos Base64. El estándar (Tabla 1) usa A-Z, a-z, 0-9, +, / con = para padding. Es lo que btoa() y la mayoría de librerías producen por defecto.

El alfabeto URL-safe (Tabla 2, frecuentemente llamado "base64url") reemplaza + por - y / por _ para evitar conflictos con la sintaxis de URLs. Opcionalmente omite el padding. Lo encontrarás en JWTs, tokens OAuth y cualquier lugar donde datos Base64 aparezcan en URLs o nombres de archivo. En Node.js, usa Buffer.from(data).toString("base64url").

MIME Base64 (usado en email) es el alfabeto estándar pero con saltos de línea cada 76 caracteres. Algunos decodificadores antiguos fallan con Base64 sin saltos de línea, y algunos modernos fallan con saltos de línea. Siempre confirma qué variante espera tu consumidor.

También existe Base32 (RFC 4648 §6) que usa solo letras mayúsculas y dígitos 2-7. Es un 60% más grande que la entrada (frente al 33% de Base64) pero es insensible a mayúsculas y evita caracteres confusos como 0/O y 1/l. Los códigos TOTP (Google Authenticator) usan Base32 para el secreto compartido porque los humanos necesitan escribirlo manualmente.

Base64 en diferentes lenguajes

JavaScript (navegador): btoa() codifica una cadena a Base64, atob() decodifica. Trampa: btoa() solo maneja caracteres Latin-1. Para cadenas UTF-8, necesitas btoa(unescape(encodeURIComponent(str))) o el enfoque moderno con TextEncoder. Desde 2024, la mayoría de entornos soportan la opción base64 en TextEncoder/TextDecoder.

Node.js: Buffer.from(data).toString("base64") para codificar, Buffer.from(b64, "base64") para decodificar. Para base64url, pasa "base64url" en su lugar. Esto maneja datos binarios correctamente sin la limitación Latin-1 de btoa().

Python: import base64; base64.b64encode(bytes_data) y base64.b64decode(b64_string). Para URL-safe: base64.urlsafe_b64encode(). Nota que estos trabajan con objetos bytes, no strings — necesitarás conversiones con .encode("utf-8") y .decode("utf-8").

Go: paquete encoding/base64 con base64.StdEncoding.EncodeToString() y base64.URLEncoding para la variante URL-safe. La implementación de Go es notablemente rápida — unas 3x más rápida que la de Python para entradas grandes gracias a la naturaleza compilada del lenguaje.

// Navegador: manejar UTF-8 correctamente
const texto = "Hola 你好 🌍";

// ❌ btoa() falla con caracteres no Latin-1
// btoa(texto) → lanza InvalidCharacterError

// ✅ Enfoque correcto para UTF-8
const codificado = btoa(
  String.fromCharCode(...new TextEncoder().encode(texto))
);

const decodificado = new TextDecoder().decode(
  Uint8Array.from(atob(codificado), c => c.charCodeAt(0))
);
// "Hola 你好 🌍"

// Node.js: mucho más simple
Buffer.from(texto).toString("base64");
Buffer.from(codificado, "base64").toString("utf-8");

Depuración de problemas con Base64

Salida ilegible tras decodificar: Probablemente tienes un desajuste de codificación de caracteres. El caso más común: los datos se codificaron como bytes UTF-8, pero estás decodificando la salida Base64 como Latin-1 (o viceversa). Siempre codifica a UTF-8 antes de aplicar Base64, y decodifica desde UTF-8 después de decodificar Base64.

Errores de "carácter inválido": Comprueba si hay espacios en blanco. El Base64 con formato MIME tiene saltos de línea (\r\n cada 76 caracteres) que decodificadores estrictos rechazan. Elimina todos los espacios antes de decodificar. También verifica la variante — una cadena base64url con - y _ fallará en un decodificador Base64 estándar.

Errores de padding: Algunos decodificadores requieren padding (=), otros lo rechazan. Si obtienes errores de "padding incorrecto", intenta añadir caracteres = hasta que la longitud de la cadena sea divisible por 4. Si obtienes "carácter inválido" en el =, intenta eliminar todos los =. El enfoque más seguro: normaliza a Base64 estándar con padding antes de decodificar.

Doble codificación: Si tu salida decodificada parece Base64 (todo alfanumérico con + / =), alguien la codificó dos veces. Decodifica de nuevo. He visto datos triplemente codificados en producción — cada capa de middleware codificaba el payload en Base64 "por seguridad". Usa nuestra herramienta Base64 para decodificar iterativamente hasta obtener texto legible.