Saltar al contenido
ecdsa.com

HS256 frente a RS256: JWT con secreto o con clave pública

HS256 es un HMAC: cualquier verificador también puede emitir tokens. RS256 es una firma: los verificadores solo tienen una clave pública. La mayoría de los errores de diseño con JWT empiezan por no advertir esa diferencia.
Qué se comparaHS256RS256

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

HS256RS256
AlgoritmoHMAC con SHA-256 (RFC 7518 §3.2)RSASSA-PKCS1-v1_5 con SHA-256 (§3.3)
ClaveUn único secreto compartido, de al menos 256 bitsPar de claves RSA, de al menos 2048 bits
Quién puede verificarLos titulares del secretoCualquiera que tenga la clave pública
Quién puede emitir tokensTodos los titulares del secreto, incluidos los verificadoresSolo el titular de la clave privada
Distribución de clavesUna copia confidencial para cada verificadorJWKS público (jwks_uri)
Segmento de la firma32 bytes, 43 caracteres base64url256 bytes, 342 caracteres (RSA-2048)
Token de ejemplo (medido más abajo)187 caracteres508 caracteres
Velocidad, un núcleo de Apple M4 Maxmillones de HMAC por segundounas 3000 firmas y 114.000 verificaciones por segundo
Nivel de requisito en JWAObligatorioRecomendado
Principal modo de falloSecreto débil o filtrado: cualquiera puede emitir tokensConfusió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.

Node.js: tokens HS256 y RS256 para la misma carga útil
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);
Salida
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:

  1. Crea el par de claves en un KMS o un HSM y publica la clave pública en un JWKS con un kid y un alg nuevos.
  2. Enseña a los verificadores a aceptar ambos: que seleccionen la clave por kid y 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.
  3. Empieza a emitir tokens asimétricos con el nuevo kid; los tokens HS256 existentes siguen verificándose hasta que caducan.
  4. 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.
  5. 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

Todas las comparaciones