HS256 vs RS256 é a primeira decisão quando você emite JWTs. Os dois valores de alg parecem semelhantes, mas o HS256 autentica os tokens com um segredo compartilhado (HMAC-SHA256), enquanto o RS256 os assina com uma chave privada RSA (RSASSA-PKCS1-v1_5 com SHA-256). Isso muda quem pode criar tokens, como as chaves são distribuídas e o que acontece quando uma chave vaza.
Visão geral
| HS256 | RS256 | |
|---|---|---|
| Algoritmo | HMAC com SHA-256 (RFC 7518 §3.2) | RSASSA-PKCS1-v1_5 com SHA-256 (§3.3) |
| Chave | Um único segredo compartilhado, de pelo menos 256 bits | Par de chaves RSA, de pelo menos 2048 bits |
| Quem pode verificar | Quem tem o segredo | Qualquer um que tenha a chave pública |
| Quem pode emitir tokens | Todo detentor do segredo, inclusive os verificadores | Apenas o detentor da chave privada |
| Distribuição de chaves | Cópia confidencial para cada verificador | JWKS público (jwks_uri) |
| Parte da assinatura | 32 bytes, 43 caracteres base64url | 256 bytes, 342 caracteres (RSA-2048) |
| Token de exemplo (medido abaixo) | 187 caracteres | 508 caracteres |
| Velocidade, um núcleo do Apple M4 Max | milhões de HMACs por segundo | cerca de 3.000 assinaturas e 114.000 verificações por segundo |
| Nível de exigência na JWA | Obrigatório | Recomendado |
| Principal modo de falha | Segredo fraco ou vazado: qualquer um pode emitir tokens | Confusão de algoritmo em verificadores permissivos; manuseio das chaves |
O modelo de confiança
Com o HS256, a chave de verificação e a chave de assinatura são os mesmos bytes. Todo serviço que verifica tokens também pode criar tokens válidos para qualquer usuário, escopo ou público-alvo, e um segredo que vaze de qualquer um deles (uma variável de ambiente em um log, um bundle do front-end, um aplicativo móvel) permite que qualquer pessoa emita tokens. Isso é aceitável para um monólito que emite e verifica os próprios tokens de sessão; é uma fraqueza séria quando um emissor de tokens atende a muitas APIs ou equipes.
Com o RS256, o emissor guarda a chave privada, de preferência em um HSM ou KMS, e publica a chave pública em um JWKS, em uma URL informada nos seus metadados. Os verificadores buscam o conjunto de chaves, selecionam as chaves pelo kid e podem ser adicionados ou removidos sem que nenhum segredo seja compartilhado. Um verificador comprometido consegue ler os tokens, mas não consegue forjá-los. É por isso que os provedores OpenID Connect assinam tokens de ID com RS256 por padrão e que a RFC de boas práticas de JWT (RFC 8725) orienta implantações com várias partes para algoritmos assimétricos.
O OpenID Connect permite, sim, tokens de ID HS256 para clientes confidenciais, com o client secret como chave; isso funciona porque cada cliente verifica apenas os tokens emitidos para ele mesmo.
Força do segredo e quebra offline
Um token HS256 é um oráculo perfeito para testes offline: qualquer pessoa que veja um único token pode testar segredos candidatos tão rápido quanto o hardware dela calcula HMAC-SHA256, sem se comunicar com o seu servidor. Segredos curtos, escolhidos por pessoas ou mantidos no valor padrão (“secret”, o nome de um projeto, o exemplo de um tutorial) são quebrados em segundos com ferramentas comuns de quebra de senhas. A RFC 7518 exige uma chave pelo menos tão longa quanto a saída do hash, 256 bits para o HS256, e a RFC 8725 §3.5 diz que as chaves devem ter entropia suficiente. Gere o segredo com um CSPRNG (openssl rand -base64 32) e nunca o derive de uma senha.
O RS256 não tem um ataque offline equivalente contra a chave privada, mas a RFC 7518 não permite chaves RSA com menos de 2048 bits, e o arquivo da chave precisa ser protegido como qualquer chave privada.
Confusão de algoritmo entre os dois
A vulnerabilidade de JWT mais conhecida mistura os dois. Um serviço verifica tokens RS256 chamando uma biblioteca com a chave pública RSA em formato PEM. Se a biblioteca lê o algoritmo do cabeçalho do token e também aceita HS256, um atacante envia um token com "alg": "HS256" cujo HMAC foi calculado usando como segredo os bytes PEM da chave pública; a biblioteca usa os mesmos bytes e a falsificação passa na verificação. Tim McLean descreveu isso em 2015, junto com "alg": "none", e a RFC 8725 (§2.1 e §3.1) formaliza a correção: quem decide com qual algoritmo cada chave pode ser usada é o verificador, não o token.
As bibliotecas modernas dificultam isso: o jsonwebtoken se recusa a usar uma chave pública como segredo de HMAC (o erro secretOrPublicKey must be an asymmetric key aparece quando as configurações misturam as duas coisas), o PyJWT exige um argumento algorithms e o jose vincula os algoritmos aos tipos de chave. Ainda assim, sempre passe uma lista de permissões explícita com exatamente o algoritmo que você espera.
Tamanho e velocidade
O script abaixo, executado com o Node.js 26.10, monta as mesmas claims como tokens HS256 e RS256 usando apenas o 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
O cabeçalho do RS256 também leva um kid, de que os verificadores precisam para escolher a chave em um JWKS. Em produção, use uma biblioteca de JWT mantida em vez de tokens montados à mão; o que importa aqui é o tamanho da parte da assinatura. Para comparar o mesmo dilema com ECDSA, veja ES256 vs RS256.
Quando escolher cada um
- Uma única aplicação que emite e verifica os próprios tokens de sessão: HS256 com um segredo aleatório de 32 bytes, rotacionado com um curto período de sobreposição.
- Um servidor de autorização cujos tokens são verificados por várias APIs: RS256, PS256 ou ES256, com as chaves publicadas em um JWKS.
- Tokens verificados por terceiros, aplicativos móveis ou navegadores: apenas algoritmos assimétricos; um segredo entregue a um cliente é público.
- Tokens de ID do OpenID Connect para clientes confidenciais: qualquer um dos dois funciona; o assimétrico é o padrão e é mais simples de operar.
- Webhooks para clientes individuais: esquemas no estilo HMAC são comuns porque cada cliente tem o próprio segredo.
- Custódia de chaves por exigência de conformidade (HSM, KMS): chaves assimétricas, já que os serviços conseguem verificar sem acesso ao material de assinatura.
Notas de migração
Para migrar de HS256 para RS256 ou ES256 sem uma virada simultânea em todos os serviços:
- Crie o par de chaves em um KMS ou HSM e publique a chave pública em um JWKS com novos
kidealg. - Ensine os verificadores a aceitar os dois: selecione a chave pelo
kide vincule cada chave a um único algoritmo (o segredo compartilhado ao HS256, a chave nova ao RS256). Nunca deixe que uma mesma chamada de verificação aceite os dois algoritmos para o mesmo material de chave. - Comece a emitir tokens assimétricos com o novo
kid; os tokens HS256 existentes continuam passando na verificação até expirarem. - Remova o segredo compartilhado de todos os verificadores depois de transcorrido o maior tempo de vida de um token HS256. Esta etapa é o ganho real de segurança.
- Rotacione o segredo antigo mesmo assim se houver qualquer chance de ele ter vazado, já que um atacante que o tenha ainda poderia emitir tokens HS256 até os verificadores deixarem de aceitá-los.
Referências
- RFC 7518, JSON Web Algorithms (§3.2 HMAC, §3.3 RSASSA-PKCS1-v1_5)
- RFC 8725, boas práticas atuais para JWT (§2.1, §3.1, §3.5)
- RFC 7519, JSON Web Token
- RFC 7517, JSON Web Key e JWK Set
- OpenID Connect Core 1.0 §10.1, assinatura com algoritmos MAC
- Auth0: vulnerabilidades críticas em bibliotecas de JSON Web Token (2015)