Pular para o conteúdo
ecdsa.com

HS256 vs RS256: JWTs com segredo ou com chave pública

O HS256 é um HMAC: todo verificador também pode emitir tokens. O RS256 é uma assinatura: os verificadores têm apenas uma chave pública. A maioria dos erros de projeto com JWT começa por não perceber essa diferença.
Itens comparadosHS256RS256

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

HS256RS256
AlgoritmoHMAC com SHA-256 (RFC 7518 §3.2)RSASSA-PKCS1-v1_5 com SHA-256 (§3.3)
ChaveUm único segredo compartilhado, de pelo menos 256 bitsPar de chaves RSA, de pelo menos 2048 bits
Quem pode verificarQuem tem o segredoQualquer um que tenha a chave pública
Quem pode emitir tokensTodo detentor do segredo, inclusive os verificadoresApenas o detentor da chave privada
Distribuição de chavesCópia confidencial para cada verificadorJWKS público (jwks_uri)
Parte da assinatura32 bytes, 43 caracteres base64url256 bytes, 342 caracteres (RSA-2048)
Token de exemplo (medido abaixo)187 caracteres508 caracteres
Velocidade, um núcleo do Apple M4 Maxmilhões de HMACs por segundocerca de 3.000 assinaturas e 114.000 verificações por segundo
Nível de exigência na JWAObrigatórioRecomendado
Principal modo de falhaSegredo fraco ou vazado: qualquer um pode emitir tokensConfusã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.

Node.js: tokens HS256 e RS256 para o mesmo payload
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);
Saída
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:

  1. Crie o par de chaves em um KMS ou HSM e publique a chave pública em um JWKS com novos kid e alg.
  2. Ensine os verificadores a aceitar os dois: selecione a chave pelo kid e 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.
  3. Comece a emitir tokens assimétricos com o novo kid; os tokens HS256 existentes continuam passando na verificação até expirarem.
  4. 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.
  5. 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

Todas as comparações