Subir un archivo
  1. base64hub
  2. Aprende Base64
  3. Codificación vs cifrado
Seguridad

Base64 no es cifrado: codificación frente a cifrado

Base64 solo cambia la forma de escribir los bytes. Todo lo que codificas en Base64 lo puede decodificar cualquiera que lo vea, con un solo comando y sin clave.

Tres operaciones distintas

Base64 aparece a menudo en sitios donde la gente espera protección, así que conviene ser exactos sobre lo que hace. Es una codificación: una correspondencia fija y pública entre bytes y texto (consulta cómo funciona Base64). No tiene clave ni secreto, y la misma entrada produce siempre la misma salida.

Codificación (Base64, hex)Cifrado (AES-GCM)Hash (SHA-256, Argon2)
ObjetivoRepresentar bytes como textoMantener los datos confidencialesObtener una huella o verificar datos
ReversibleSí, por cualquieraSí, con la claveNo
Necesita claveNoSíNo (HMAC usa una)
Misma entrada, misma salidaSiempreNo, con un nonce aleatorioSí, salvo que lleve salt
Tamaño de salidaUnos 4/3 de la entradaEntrada más nonce y etiquetaLongitud fija

Decodificar no cuesta ningún esfuerzo:

echo ZGVwbG95Omh1bnRlcjI= | base64 -d
# deploy:hunter2

Esa cadena parece opaca a alguien que la mira de pasada. Para un script, un escáner de logs o un atacante, es texto plano con un paso más.

Una prueba útil: si un valor sería un problema en una captura de pantalla, sigue siéndolo después de codificarlo en Base64.

Cómo se filtran en la práctica los datos en Base64

El patrón es siempre el mismo: un valor se codifica en Base64 para transportarlo, alguien lo toma por protegido y acaba en algún sitio donde se puede leer. Estas son las formas habituales de ese error.

Cabeceras HTTP Basic Auth

La autenticación Basic (RFC 7617) envía el usuario y la contraseña como Base64(user:password) en cada petición:

Authorization: Basic ZGVwbG95Omh1bnRlcjI=

La única protección es el TLS de la conexión. En cuanto la cabecera sale de ese túnel, la contraseña está prácticamente en claro. Formas típicas en que ocurre: proxies inversos o frameworks de aplicaciones que registran las cabeceras de las peticiones, archivos HAR exportados desde las herramientas para desarrolladores del navegador y adjuntados a un ticket de soporte, salida de curl -v pegada en un chat y herramientas de seguimiento de errores que capturan peticiones completas. El decodificador Basic Auth muestra lo poco que cuesta leer una.

Secrets de Kubernetes

Un Secret de Kubernetes guarda sus valores codificados en Base64:

apiVersion: v1
kind: Secret
metadata:
  name: db
data:
  password: czNjcjN0

czNjcjN0 es s3cr3t. La codificación existe para que los valores puedan contener datos binarios dentro de YAML. La documentación de Kubernetes indica que, por defecto, los Secrets se guardan sin cifrar en etcd, y cualquiera con permiso para hacer get del Secret a través de la API puede leerlo. Las filtraciones habituales son manifiestos con valores data: reales subidos a un repositorio Git, salida de kubectl get secret -o yaml pegada en tickets y copias de seguridad de etcd o del estado del clúster guardadas sin cifrar. El decodificador de Secrets de Kubernetes decodifica a la vez todos los valores de un Secret, lo que también es una forma rápida de comprobar qué expone realmente un manifiesto.

Las credenciales de los registros de contenedores siguen el mismo patrón. El campo auth de un config.json de Docker o de un Secret .dockerconfigjson es Base64(user:password); el decodificador de dockerconfigjson lo lee.

Cargas útiles de JWT

Un JWT firmado parece un token sellado, pero sus dos primeras partes son simplemente JSON en Base64URL:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsImVtYWlsIjoiYW5hQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIn0.<signature>

La carga útil se decodifica como {"sub":"42","email":"ana@example.com","role":"admin"}. La firma protege el token contra modificaciones; no hace nada por la confidencialidad. Cualquier extensión del navegador, proxy, archivo de log o script de analítica que vea el token puede leer lo que contiene. Los datos personales, los identificadores internos que prefieres no exponer y cualquier cosa secreta no deben ir en la carga útil de un JWT. Si tienen que viajar en un token, usa un JWT cifrado (JWE). Más detalles en Base64 en JWT.

Secretos «ofuscados» en código y configuración

Las claves de API guardadas en Base64 en el JavaScript del front-end, en paquetes de apps móviles o en archivos de configuración son un clásico. El razonamiento habitual es que así la clave no está «en texto plano». Lo único que consigue es que no aparezca en una búsqueda ingenua de la clave literal. Todo lo que se envía a un cliente lo puede leer ese cliente.

La misma idea funciona en sentido contrario en los ataques. El -EncodedCommand de PowerShell recibe Base64 de texto UTF-16LE, y se usa a menudo para que los comandos maliciosos sean más difíciles de leer a simple vista. Los defensores lo decodifican en segundos, por ejemplo con el decodificador de comandos codificados de PowerShell.

Qué usar en su lugar

Elige la herramienta según el trabajo. Base64 sigue teniendo su sitio en cada uno de estos casos, como envoltorio de texto para bytes protegidos por otra cosa.

Para mantener datos confidenciales: cifrado autenticado

Usa AES-256-GCM o ChaCha20-Poly1305 a través de una biblioteca bien mantenida. Ambos están autenticados, así que cualquier manipulación se detecta al descifrar. Hay dos reglas que importan más que el algoritmo: no reutilices nunca un nonce con la misma clave, y guarda la clave en un lugar distinto de los datos (un KMS, un gestor de secretos o un entorno que el atacante no controle también).

import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";

const key = randomBytes(32);   // in real code, load this from a KMS or secret store
const iv = randomBytes(12);    // 96-bit nonce, unique per message

const cipher = createCipheriv("aes-256-gcm", key, iv);
const ct = Buffer.concat([cipher.update("deploy:hunter2", "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();

// Base64 carries the encrypted bytes as text: nonce + tag + ciphertext
const token = Buffer.concat([iv, tag, ct]).toString("base64");

const raw = Buffer.from(token, "base64");
const d = createDecipheriv("aes-256-gcm", key, raw.subarray(0, 12));
d.setAuthTag(raw.subarray(12, 28));
console.log(Buffer.concat([d.update(raw.subarray(28)), d.final()]).toString("utf8"));
// deploy:hunter2

La salida es una cadena Base64 de 56 caracteres que cambia en cada ejecución porque el nonce es aleatorio. Sin la clave no sirve de nada. Para experimentar sin escribir código, las herramientas Cifrar AES y Descifrar AES funcionan en tu navegador.

Para guardar contraseñas: un hash lento y con salt

Las contraseñas no deben cifrarse ni codificarse. Guarda un hash de una función diseñada para contraseñas: Argon2id, scrypt o bcrypt, con un salt aleatorio único por contraseña. Un simple SHA-256 de una contraseña se calcula demasiado rápido y cae ante la fuerza bruta. La biblioteca estándar de Python incluye scrypt:

import hashlib, hmac, os

def hash_password(pw: str) -> bytes:
    salt = os.urandom(16)
    return salt + hashlib.scrypt(pw.encode(), salt=salt, n=2**14, r=8, p=1)

def check_password(pw: str, stored: bytes) -> bool:
    salt, digest = stored[:16], stored[16:]
    candidate = hashlib.scrypt(pw.encode(), salt=salt, n=2**14, r=8, p=1)
    return hmac.compare_digest(candidate, digest)

Si necesitas guardar el resultado en una columna de texto o en JSON, pásalo a Base64.

Para demostrar integridad: firmas y HMAC

Cuando el objetivo es «nadie ha cambiado esto» y no «nadie puede leer esto», usa un HMAC con un secreto compartido o una firma digital. El resultado son bytes, que normalmente se transportan en Base64 o hex. El generador HMAC muestra la salida en ambas formas.

Para credenciales en tránsito y en reposo

Usa TLS en todas las conexiones que lleven Basic Auth o tokens, prefiere tokens de corta duración a contraseñas estáticas, elimina las cabeceras Authorization de los logs, activa el cifrado en reposo de los Secrets de Kubernetes y mantén los valores reales de los secretos fuera de Git.

Preguntas frecuentes

¿Base64 es seguro?

Base64 no tiene ninguna propiedad de seguridad. No hay clave, así que para decodificar basta con la propia cadena. Es seguro usarlo como formato de transporte para datos que ya están protegidos, como un texto cifrado o una firma.

¿Basta Base64 para ocultar una contraseña en un archivo de configuración?

No. Cualquiera que pueda leer el archivo puede decodificar la contraseña con base64 -d. Restringe los permisos del archivo, usa un gestor de secretos o cifra el valor con una clave guardada en otro sitio.

¿Por qué los Secrets de Kubernetes van en Base64 si no es por seguridad?

Para que los valores de un Secret puedan contener datos binarios arbitrarios, como claves TLS o almacenes de claves, dentro de YAML y JSON. La protección viene de RBAC, del cifrado en reposo de etcd y del manejo cuidadoso de los manifiestos.

¿Hace falta el secreto para leer la carga útil de un JWT?

No. La cabecera y la carga útil son JSON en Base64URL que cualquiera puede leer. La clave solo hace falta para verificar o crear la firma. Pruébalo con el decodificador JWT.

¿Qué diferencia hay entre codificar, cifrar y calcular un hash?

La codificación la puede revertir cualquiera. El cifrado solo se revierte con una clave. El hash va en un solo sentido: puedes comprobar un valor contra un hash, pero no puedes convertir el hash de nuevo en el valor.

Sigue leyendo