← Todos los artículos

Base64 no es cifrado: qué oculta y qué no

Base64 es codificación, no cifrado. Por qué contraseñas, payloads JWT, claves API, cabeceras Basic Auth, URLs y logs siguen legibles tras Base64.

"Podemos codificarlo en Base64 antes de guardarlo." Si revisas suficientes ficheros de config, paneles internos, bundles de navegador, handlers de webhooks o código de autenticación, tarde o temprano oyes esa frase. Suena plausible porque la salida parece revuelta:

c2VjcmV0IHBhc3N3b3Jk

Pero Base64 no es cifrado. No es protección con contraseña. No es una forma segura de esconder claves de API, claims de JWT, IDs de usuario, credenciales de Basic Auth ni datos privados en una URL. El mismo valor se decodifica en una sola llamada:

atob("c2VjcmV0IHBhc3N3b3Jk");
// "secret password"

La regla práctica es directa: si el valor original vuelve sin ninguna clave secreta, Base64 no lo protegió. Solo cambió la representación.

El test rápido de seguridad

Cuando veas Base64 en el código, pregúntate qué trabajo está haciendo:

Base64 se está usando para... ¿Buen uso? Qué revisar
Meter bytes en JSON Mantener límites de tamaño razonables
Incrustar una data: URI pequeña No incluir archivos grandes a ciegas
Guardar como texto ciphertext, nonce, salt o bytes de firma El paso criptográfico debe ir antes de Base64
Llevar un cursor de paginación opaco Normalmente Trátalo como detalle de interfaz, no como control de acceso
Construir una cabecera Basic Auth Solo con HTTPS TLS protege la petición, Base64 no
Ocultar una clave de API en el frontend No Todo navegador recibe el valor y el decodificador
Guardar una contraseña No Usa hashing lento de contraseñas
Ocultar claims de JWT No Los payloads JWT firmados son legibles salvo que se cifren como JWE
Ocultar IDs o roles de usuario en URLs No La autorización en servidor sigue siendo obligatoria

Si la frase que tienes en la cabeza es "para que nadie lo lea", no estás describiendo Base64. Estás describiendo cifrado, hashing, firma, control de acceso o gestión de secretos.

Qué hace Base64 realmente

Base64 es un esquema de codificación binario a texto. RFC 4648 define el alfabeto Base64 habitual como:

A-Z a-z 0-9 + / =

El carácter = es padding. Base64 agrupa los bytes de entrada en bloques de 24 bits: 3 bytes de entrada se convierten en 4 caracteres imprimibles. Por eso la salida Base64 es aproximadamente un 33 % mayor que los bytes en crudo antes de comprimir.

btoa("hello");
// "aGVsbG8="

atob("aGVsbG8=");
// "hello"

El mismo viaje de ida y vuelta funciona desde una terminal:

printf '%s' 'secret password' | base64
# c2VjcmV0IHBhc3N3b3Jk

printf '%s' 'c2VjcmV0IHBhc3N3b3Jk' | base64 -d
# secret password

No hay prompt de contraseña, clave privada, paso de descifrado, salt, nonce, IV, tag ni factor de coste. Quien tenga la cadena codificada puede invertirla.

Base64 vs Base64url

El Base64 estándar no siempre es seguro para URLs porque +, / y = resultan incómodos en URLs, nombres de fichero, cookies y segmentos de token. Base64url cambia el alfabeto:

Característica Base64 estándar Base64url
Caracteres 62 y 63 + y / - y _
Padding Normalmente = Suele omitirse
Sitios habituales MIME, PEM, Basic Auth, campos binarios JWTs, tokens en URL, nombres de fichero
Diferencia de seguridad Ninguna Ninguna

Esto importa cuando depuras JWTs. Un JWT usa Base64url, no Base64 estándar con padding:

header.payload.signature

Si pegas un segmento de JWT en un decodificador Base64 estándar y falla, la cadena no tiene por qué ser secreta ni estar cifrada. Puede que simplemente necesite tratamiento Base64url:

function base64urlToBase64(part) {
  let out = part.replace(/-/g, "+").replace(/_/g, "/");
  while (out.length % 4 !== 0) out += "=";
  return out;
}

Esa conversión cambia el alfabeto. No añade confidencialidad.

UTF-8: la trampa del navegador

Los btoa() y atob() del navegador trabajan sobre cadenas binarias. Los ejemplos ASCII simples van bien:

btoa("hello");
// "aGVsbG8="

Pero el texto Unicode necesita un paso previo a bytes. Esto pilla por sorpresa a equipos que prueban con hello y más adelante codifican nombres de cliente, texto en chino, emoji o árabe:

btoa("你好");
// InvalidCharacterError in browsers

Usa bytes UTF-8:

function utf8ToBase64(text) {
  const bytes = new TextEncoder().encode(text);
  const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join("");
  return btoa(binary);
}

function base64ToUtf8(base64) {
  const binary = atob(base64);
  const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}

const encoded = utf8ToBase64("你好");
base64ToUtf8(encoded);
// "你好"

La herramienta Base64 Encode & Decode de este sitio hace el viaje UTF-8 en el navegador, así el texto no ASCII vuelve como texto y no como mojibake.

Por qué los desarrolladores confunden Base64 con cifrado

Base64 aparece a menudo cerca de seguridad real:

  • Los certificados TLS se guardan en texto PEM.
  • Los JWTs usan secciones Base64url.
  • HTTP Basic Auth usa Base64 para username:password.
  • Las claves públicas SSH contienen material de clave codificado en Base64.
  • El ciphertext cifrado se suele codificar en Base64 antes de guardarlo o transportarlo.

Este último punto causa la mayor parte de la confusión. Los datos cifrados pueden ir codificados en Base64, pero Base64 no es el cifrado. La seguridad vino de una operación criptográfica como AES-GCM, XChaCha20-Poly1305, RSA-OAEP, JWE o TLS. Base64 solo hizo que los bytes resultantes fueran fáciles de guardar en JSON, correo, cabeceras o una columna de base de datos.

Usa palabras precisas en el código y en los textos de producto:

encodedBackup      // OK si solo es Base64
encryptedBackup    // Solo OK si hubo cifrado real
signedState        // OK si un HMAC o firma protege la integridad
passwordHash       // OK si lo produjo un algoritmo de hashing de contraseñas

Los nombres aquí no son cosmética. Un nombre engañoso puede hacer que un futuro revisor asuma que existe una frontera de seguridad cuando no la hay.

Codificación vs cifrado vs hashing vs firma

Estas operaciones resuelven problemas distintos:

Operación Trabajo principal ¿Requiere secreto? ¿Reversible? Ejemplo
Codificación Cambiar la representación No Base64, Base64url, URL encoding
Cifrado Ocultar el texto claro Sí, con clave AES-GCM, XChaCha20-Poly1305, JWE
Hashing Huella de una vía No No SHA-256 para checksums
Hashing de contraseñas Verificación lenta de contraseñas Salt más parámetros de coste No Argon2id, bcrypt, scrypt
Firma / MAC Probar integridad y origen Sí, o par de claves No HMAC, JWS, firma Ed25519

Base64 responde a "¿puede esta secuencia de bytes viajar por un canal de texto?". No responde a "¿quién puede leer esto?", "¿quién cambió esto?" ni "¿tiene este usuario permiso para ese registro?".

Error real de seguridad 1: claves de API escondidas en el frontend

Este patrón sale en apps React, extensiones de navegador, temas de WordPress y herramientas de admin internas:

// La clave sigue llegando a cada navegador.
const apiKey = atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=");

fetch("https://api.example.com/report", {
  headers: { Authorization: `Bearer ${apiKey}` },
});

Base64 no ayuda porque el navegador recibe tanto el valor codificado como el decodificador. Cualquiera puede abrir DevTools, inspeccionar el bundle JavaScript, ejecutar la misma llamada a atob() y copiar la clave.

El arreglo es arquitectónico:

  • Mantén los secretos reales en el servidor.
  • Da a las claves de navegador scopes estrechos y restricciones por dominio.
  • Proxya las llamadas sensibles a través de tu backend.
  • Rota cualquier secreto que ya haya llegado a los usuarios.
  • Asume que los escáneres de secretos saben decodificar cadenas parecidas a Base64.

Codificar una clave puede complicar el code review. No hace la clave más segura.

Error real de seguridad 2: contraseñas guardadas como Base64

Esto es dañino porque puede quedarse en silencio en una base de datos durante años:

// Almacenamiento reversible. No hagas esto.
const storedPassword = btoa(password);

// Después:
const password = atob(storedPassword);

Si se filtra la base de datos, cada contraseña vuelve al instante. No hay coste de crackeo porque no hay nada que crackear.

El almacenamiento de contraseñas debe ser de una vía:

// Forma del flujo, usando una librería de hashing de contraseñas.
const hash = await argon2.hash(password);
const ok = await argon2.verify(hash, candidatePassword);

Los sistemas normales de login nunca deberían necesitar recuperar la contraseña del usuario. Solo necesitan verificar una contraseña candidata contra un hash guardado.

Error real de seguridad 3: Basic Auth confundido con cifrado

La cabecera parece lo bastante codificada como para engañar:

Authorization: Basic dXNlcjpwYXNzd29yZA==

Decodifica a:

atob("dXNlcjpwYXNzd29yZA==");
// "user:password"

HTTP Basic Auth solo es aceptable cuando la petición va protegida por HTTPS. TLS cifra el intercambio HTTP en tránsito. Base64 solo empaqueta username:password en una cadena ASCII apta para cabeceras.

Sobre HTTP plano, un observador de red puede leer las credenciales. En logs, proxies inversos, trazas APM y volcados de soporte, una cabecera Basic Auth debe tratarse como una contraseña en claro.

Error real de seguridad 4: payloads de JWT tratados como privados

Un JWT firmado normal tiene tres secciones Base64url:

header.payload.signature

Las dos primeras decodifican a JSON. La tercera es la firma.

eyJzdWIiOiIxMjM0Iiwicm9sZSI6ImFkbWluIn0

Decodifica a:

{
  "sub": "1234",
  "role": "admin"
}

Eso no significa que el token sea falso. Un JWT firmado puede ser evidente a manipulación y a la vez legible. La firma protege la integridad: le dice al servidor si el token lo emitió una parte de confianza y si la cabecera o el payload cambiaron. No oculta el payload.

No metas contraseñas, claves de API, secretos de sesión, números completos de tarjeta, detalles médicos ni datos privados de perfil en un payload de JWT firmado normal. Si los claims necesitan confidencialidad, usa JWE o evita meter esos datos en el token.

Además: decodificar no es verificar. Una herramienta de dev puede mostrar claims; un servidor sigue teniendo que verificar la firma, el issuer, la audience, la expiración, el key ID, la política de algoritmo y las reglas de autorización propias de la app antes de confiar en ellos.

Error real de seguridad 5: parámetros de URL que parecen opacos

Las apps legacy suelen pasar estado así:

/profile?data=eyJ1c2VySWQiOjQyfQ==

Eso decodifica a:

{
  "userId": 42
}

Salen dos problemas. Cualquiera puede leerlo y cualquiera puede cambiarlo y volverlo a codificar. Si /profile?data=... es lo único que decide qué registro de usuario cargar, el bug es autorización ausente.

Patrones más seguros:

  • Trata los datos decodificados de la URL como input no confiable.
  • Vuelve a comprobar los permisos en el servidor.
  • Prefiere una referencia corta y aleatoria en el servidor cuando el estado sea sensible.
  • Usa un HMAC o una firma si el cliente debe llevar estado evidente a manipulación.
  • Mantén el estado privado fuera de las URLs, porque las URLs acaban copiadas en logs, historial de navegador, analítica y capturas de soporte.

Error real de seguridad 6: exports "cifrados" que solo están codificados

Otro olor de code review es una función así:

function exportEncryptedBackup(data) {
  return btoa(JSON.stringify(data));
}

El nombre dice cifrado. El código dice codificado.

Si el export solo pretende ser un blob de texto portable, renómbralo:

function exportBase64Backup(data) {
  return btoa(JSON.stringify(data));
}

Si a los usuarios se les está diciendo que el export está cifrado, usa cifrado autenticado real y haz explícita la gestión de claves. Eso significa una librería de cripto revisada, nonces/IVs aleatorios, tags de autenticación, generación segura de claves y un plan de almacenamiento de claves. Base64 puede envolver el ciphertext después.

Para qué sirve Base64 realmente

Base64 es útil. El error es asignarle un trabajo de seguridad.

Buenos usos:

  • Datos binarios dentro de JSON, XML, HTML o correo.
  • Adjuntos MIME de correo.
  • data: URIs pequeñas para imágenes o fuentes.
  • Formateo de salida criptográfica para ciphertext, firmas, nonces, salts y claves públicas.
  • Segmentos de serialización compacta JWT y JOSE.
  • Cursores de paginación opacos donde la legibilidad es asunto de producto, no una frontera de seguridad.
  • Depuración de campos de API, payloads JWT y blobs de config.

La formulación segura es "codificado en Base64", no "cifrado". Esa sola palabra evita una cantidad sorprendente de malentendidos en producción.

Qué usar en su lugar

Si echaste mano de Base64 porque querías seguridad, elige la primitiva que encaje con el trabajo:

Objetivo Usa en su lugar
Ocultar datos a usuarios o atacantes Cifrado autenticado como AES-GCM o XChaCha20-Poly1305
Proteger datos en tránsito HTTPS/TLS
Guardar contraseñas Argon2id, bcrypt o scrypt con salt por contraseña y coste ajustado
Guardar claves de API Gestor de secretos, entorno del servidor, scopes, logs de auditoría y rotación
Probar que un valor no cambió HMAC o firma digital
Evitar que los usuarios editen estado en la URL Autorización en servidor más estado firmado o referencia de sesión en servidor
Mantener privados los claims de un JWT JWE, o no meter claims privados en el token
Meter binarios en JSON, correo o una cabecera Base64 es lo correcto

La parte difícil rara vez es el nombre del algoritmo. Es modelar amenazas, gestionar claves, rotarlas, controlar accesos y decidir quién debe poder ver el texto en claro para empezar.

Checklist práctica de code review

Busca usos de Base64:

rg "btoa\\(|atob\\(|base64|Buffer\\.from\\(.*base64|toString\\('base64'\\)"

Clasifica cada resultado:

  1. Transporte: conversión de bytes a texto para JSON, MIME, data URI, PEM o almacenamiento. Suele ir bien.
  2. Formateo tras cripto: ciphertext, nonce, firma, salt o clave pública. Suele ir bien si la cripto es correcta.
  3. Interfaz opaca: cursor, código de invitación o estado que carga el cliente. Revisa autorización y protección contra manipulación.
  4. Ocultar secretos: clave de API, contraseña, token, credencial, claim privado o datos de usuario. No va bien.
  5. Nomenclatura engañosa: variables o textos de UI dicen cifrado, seguro, protegido, secreto, enmascarado u oculto. Arregla el nombre o la implementación.

Después pregunta:

  • ¿Recibiría el valor codificado un navegador, una app móvil, un usuario, un colector de logs, un proxy o una herramienta de soporte?
  • ¿Hay un decodificador al lado?
  • ¿Un servidor verifica permisos tras decodificar?
  • ¿El valor va firmado si el cliente puede editarlo?
  • Si es un JWT, ¿se verifica la firma antes de cualquier decisión de confianza?
  • Si es una contraseña, ¿por qué es reversible en absoluto?

Esto mantiene el review con los pies en la tierra. Un PNG en una data URI es aburrido. Una clave de API de producción en un bundle JavaScript no.

Respuesta a incidentes: si encuentras "secretos" en Base64

Si descubres un secreto codificado en Base64 en código fuente, logs, un ticket, analítica, una URL o un export de base de datos:

  1. Decodifícalo localmente para confirmar qué es.
  2. Trata el valor decodificado como expuesto.
  3. Rota credenciales, tokens y claves que hayan podido quedar a la vista.
  4. Elimina la copia codificada de código fuente, logs, capturas o tickets cuando puedas.
  5. Sustituye el diseño por un secreto en servidor, una clave pública con scope, estado firmado, cifrado real o hash de contraseña.
  6. Añade una regla de review para que el mismo patrón no vuelva.

No pierdas tiempo debatiendo si la cadena Base64 era "difícil de notar". Los escáneres automáticos, los usuarios del navegador y los atacantes pueden decodificarla igual.

Cuándo necesitas realmente un token cifrado

Un JWT firmado estándar usa JWS: payload legible, firma evidente a manipulación. Si de verdad necesitas confidencialidad de los claims, usa JWE (JSON Web Encryption).

Un JWE compacto tiene cinco secciones Base64url:

protected-header.encrypted-key.iv.ciphertext.authentication-tag

Esas secciones siguen siendo texto Base64url. La confidencialidad viene del algoritmo de cifrado JWE y de la clave del destinatario, no de Base64url en sí.

En muchas apps web, un diseño más simple funciona mejor: guarda el estado sensible en el servidor y manda al navegador un identificador de sesión corto y aleatorio. No todo JWT legible tiene que convertirse en JWE.

Preguntas frecuentes

¿Base64 es cifrado?

No. Base64 es codificación reversible sin clave secreta. Cualquiera con la cadena puede decodificarla con una función del navegador, un comando de terminal, una herramienta online o unas pocas líneas de código.

¿Al menos Base64 sirve como ofuscación?

Solo en el sentido más débil. Puede que oculte el valor de un vistazo casual, pero no detendrá a un desarrollador, un atacante, un escáner, una extensión de navegador, un lector de logs ni una herramienta de soporte.

¿Base64 es seguro para contraseñas o claves de API?

No. Las contraseñas necesitan hashing lento como Argon2id, bcrypt o scrypt. Las claves de API deben quedarse en el servidor o en un gestor de secretos con scopes, auditoría y rotación.

¿Cualquiera puede leer el payload de un JWT?

Sí, en los JWTs firmados corrientes. La cabecera y el payload son JSON codificado en Base64url. La firma puede probar la integridad tras verificarse, pero no oculta los claims.

¿Por qué Basic Auth usa Base64 si no es seguro?

Basic Auth usa Base64 para meter username:password en una cabecera HTTP. HTTPS/TLS aporta el cifrado en tránsito. Sin HTTPS, las credenciales de Basic Auth son básicamente texto plano en la red.

¿Base64url es más seguro que Base64?

No. Base64url sustituye + y / por - y _ y suele omitir el padding para que el valor funcione en URLs y JWTs. Sigue siendo codificación reversible.

¿Qué debería usar en vez de Base64 para cifrar?

Un diseño de cifrado autenticado revisado como AES-GCM, XChaCha20-Poly1305 o JWE, más generación de claves adecuada, almacenamiento de claves, rotación y control de acceso. No diseñes tu propio formato criptográfico.

¿Para qué sirve realmente Base64?

Base64 representa bytes como texto: campos binarios en JSON, adjuntos de correo, data URIs, ficheros PEM, firmas, formato de ciphertext y segmentos de token seguros para URL. Es un envoltorio, no una frontera de seguridad.

Codifica y decodifica en tu navegador

¿Necesitas inspeccionar una cadena Base64 ahora mismo? Base64 Encode & Decode en fixjson.org permite codificar o decodificar texto localmente en el navegador, incluyendo contenido UTF-8. Úsalo para inspeccionar payloads JWT, campos de respuestas de API o data: URIs, pero no confundas decodificar con verificar o descifrar.

Herramientas y guías relacionadas

Fuentes

Última revisión julio de 2026.