HS256 frente a RS256 es la primera decisión que tomas al emitir JWT. Los dos valores de alg se parecen, pero HS256 autentica los tokens con un secreto compartido (HMAC-SHA256), mientras que RS256 los firma con una clave privada RSA (RSASSA-PKCS1-v1_5 con SHA-256). Eso cambia quién puede crear tokens, cómo se distribuyen las claves y qué ocurre cuando se filtra una clave.
De un vistazo
| HS256 | RS256 | |
|---|---|---|
| Algoritmo | HMAC con SHA-256 (RFC 7518 §3.2) | RSASSA-PKCS1-v1_5 con SHA-256 (§3.3) |
| Clave | Un único secreto compartido, de al menos 256 bits | Par de claves RSA, de al menos 2048 bits |
| Quién puede verificar | Los titulares del secreto | Cualquiera que tenga la clave pública |
| Quién puede emitir tokens | Todos los titulares del secreto, incluidos los verificadores | Solo el titular de la clave privada |
| Distribución de claves | Una copia confidencial para cada verificador | JWKS público (jwks_uri) |
| Segmento de la firma | 32 bytes, 43 caracteres base64url | 256 bytes, 342 caracteres (RSA-2048) |
| Token de ejemplo (medido más abajo) | 187 caracteres | 508 caracteres |
| Velocidad, un núcleo de Apple M4 Max | millones de HMAC por segundo | unas 3000 firmas y 114.000 verificaciones por segundo |
| Nivel de requisito en JWA | Obligatorio | Recomendado |
| Principal modo de fallo | Secreto débil o filtrado: cualquiera puede emitir tokens | Confusión de algoritmos en verificadores poco estrictos; gestión de las claves |
El modelo de confianza
Con HS256, la clave de verificación y la clave de firma son los mismos bytes. Cada servicio que verifica tokens también puede crear tokens válidos para cualquier usuario, ámbito o audiencia, y un secreto filtrado desde cualquiera de ellos (una variable de entorno en un log, un bundle del frontend, una aplicación móvil) permite a cualquiera emitir tokens. Eso es aceptable en un monolito que emite y comprueba sus propios tokens de sesión; es una debilidad grave cuando un emisor de tokens da servicio a muchas API o equipos.
Con RS256, el emisor guarda la clave privada, idealmente en un HSM o un KMS, y publica la clave pública en un JWKS, en una URL que figura en sus metadatos. Los verificadores descargan el conjunto de claves y seleccionan las claves por kid, y se pueden añadir o retirar verificadores sin compartir secretos. Un verificador comprometido puede leer los tokens, pero no falsificarlos. Por eso los proveedores de OpenID Connect firman los tokens de ID con RS256 por defecto, y por eso el RFC de buenas prácticas de JWT (RFC 8725) orienta los despliegues con varias partes hacia los algoritmos asimétricos.
OpenID Connect sí permite tokens de ID HS256 para clientes confidenciales, con el secreto de cliente como clave; funciona porque cada cliente solo verifica los tokens emitidos para él.
Robustez del secreto y ataques de fuerza bruta sin conexión
Un token HS256 es un oráculo perfecto para hacer pruebas sin conexión: cualquiera que vea un solo token puede probar secretos candidatos tan rápido como su hardware calcule HMAC-SHA256, sin comunicarse con tu servidor. Los secretos cortos, elegidos por una persona o que vienen por defecto («secret», el nombre de un proyecto, el ejemplo de un tutorial) se descubren en segundos con las herramientas habituales para romper contraseñas. El RFC 7518 exige una clave al menos tan larga como la salida del hash, 256 bits para HS256, y el RFC 8725 §3.5 indica que las claves deben tener suficiente entropía. Genera el secreto con un CSPRNG (openssl rand -base64 32) y nunca lo derives de una contraseña.
RS256 no tiene un ataque sin conexión equivalente contra la clave privada, pero el RFC 7518 no permite claves RSA de menos de 2048 bits, y el archivo de la clave debe protegerse como cualquier clave privada.
Confusión de algoritmos entre ambos
La vulnerabilidad de JWT más conocida mezcla los dos. Un servicio verifica tokens RS256 llamando a una biblioteca con la clave pública RSA en formato PEM. Si la biblioteca toma el algoritmo del encabezado del token y además admite HS256, un atacante envía un token con "alg": "HS256" cuyo HMAC se calculó usando como secreto los bytes PEM de la clave pública; la biblioteca usa esos mismos bytes y la falsificación supera la verificación. Tim McLean lo describió en 2015 junto con "alg": "none", y el RFC 8725 §2.1 y §3.1 recogen la solución: es el verificador, no el token, quien decide con qué algoritmo puede usarse cada clave.
Las bibliotecas modernas lo ponen difícil: jsonwebtoken se niega a usar una clave pública como secreto HMAC (el error secretOrPublicKey must be an asymmetric key aparece cuando una configuración las confunde), PyJWT exige un argumento algorithms y jose vincula los algoritmos a los tipos de clave. Aun así, pasa siempre una lista explícita de algoritmos permitidos con exactamente el algoritmo que esperas.
Tamaño y velocidad
El script siguiente, ejecutado con Node.js 26.10, construye los mismos claims como tokens HS256 y RS256 usando solo node:crypto.
import { createHmac, generateKeyPairSync, randomBytes, sign } from "node:crypto";
const b64url = (value) => Buffer.from(value).toString("base64url");
const payload = b64url(JSON.stringify({ iss: "https://auth.example.com", sub: "user-42", aud: "api", exp: 1792500000 }));
// HS256: HMAC-SHA256 with a shared 256-bit secret.
const secret = randomBytes(32);
const hsInput = `${b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }))}.${payload}`;
const hs256 = `${hsInput}.${b64url(createHmac("sha256", secret).update(hsInput).digest())}`;
// RS256: RSASSA-PKCS1-v1_5 with SHA-256 and a 2048-bit key.
const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
const rsInput = `${b64url(JSON.stringify({ alg: "RS256", typ: "JWT", kid: "2026-10" }))}.${payload}`;
const rs256 = `${rsInput}.${b64url(sign("sha256", Buffer.from(rsInput), privateKey))}`;
console.log("HS256 token:", hs256.length, "characters, signature part:", hs256.split(".")[2].length);
console.log("RS256 token:", rs256.length, "characters, signature part:", rs256.split(".")[2].length);
HS256 token: 187 characters, signature part: 43
RS256 token: 508 characters, signature part: 342
El encabezado RS256 lleva además un kid, que los verificadores necesitan para elegir la clave dentro de un JWKS. En producción, usa una biblioteca JWT mantenida en lugar de tokens construidos a mano; aquí lo que importa es el tamaño del segmento de la firma. Para ver la misma disyuntiva con ECDSA, consulta ES256 frente a RS256.
Cuándo elegir cada uno
- Una sola aplicación que emite y comprueba sus propios tokens de sesión: HS256 con un secreto aleatorio de 32 bytes, rotado con un breve periodo de solapamiento.
- Un servidor de autorización cuyos tokens comprueban varias API: RS256, PS256 o ES256, con las claves publicadas en un JWKS.
- Tokens que verifican terceros, aplicaciones móviles o navegadores: solo algoritmos asimétricos; un secreto que se entrega a un cliente es público.
- Tokens de ID de OpenID Connect para clientes confidenciales: sirve cualquiera de los dos; el asimétrico es el predeterminado y resulta más sencillo de operar.
- Webhooks a clientes individuales: los esquemas de tipo HMAC son habituales porque cada cliente tiene su propio secreto.
- Custodia de claves impuesta por el cumplimiento normativo (HSM, KMS): claves asimétricas, ya que los servicios pueden verificar sin acceder al material de firma.
Notas de migración
Cómo pasar de HS256 a RS256 o ES256 sin un cambio simultáneo en todos los servicios:
- Crea el par de claves en un KMS o un HSM y publica la clave pública en un JWKS con un
kidy unalgnuevos. - Enseña a los verificadores a aceptar ambos: que seleccionen la clave por
kidy vinculen cada clave a un único algoritmo (el secreto compartido a HS256, la clave nueva a RS256). Nunca permitas que una misma llamada de verificación acepte los dos algoritmos para el mismo material de clave. - Empieza a emitir tokens asimétricos con el nuevo
kid; los tokens HS256 existentes siguen verificándose hasta que caducan. - Elimina el secreto compartido de todos los verificadores una vez transcurrido el periodo de validez más largo de los tokens HS256. Este paso es la verdadera mejora de seguridad.
- Rota el secreto antiguo de todos modos si existe alguna posibilidad de que se haya filtrado, ya que un atacante que lo tenga podría seguir emitiendo tokens HS256 hasta que los verificadores dejen de aceptarlos.
Referencias
- RFC 7518, JSON Web Algorithms (§3.2 HMAC, §3.3 RSASSA-PKCS1-v1_5)
- RFC 8725, buenas prácticas actuales para JWT (§2.1, §3.1, §3.5)
- RFC 7519, JSON Web Token
- RFC 7517, JSON Web Key y conjunto de JWK
- OpenID Connect Core 1.0 §10.1, firma con algoritmos MAC
- Auth0: vulnerabilidades críticas en bibliotecas de JSON Web Token (2015)