JWS vs JWE é a questão do que um token JOSE protege. Uma JSON Web Signature (RFC 7515) acrescenta integridade e autenticidade a um payload que continua legível; uma JSON Web Encryption (RFC 7516) acrescenta confidencialidade. Quase todo JWT que você encontra é um JWS, o que surpreende quem espera que as claims sejam secretas.
Visão geral
| JWS | JWE | |
|---|---|---|
| Especificação | RFC 7515 | RFC 7516 |
| Protege | Integridade e origem | Confidencialidade (e integridade por meio do AEAD) |
| Forma compacta | header.payload.signature (3 partes) | header.encrypted_key.iv.ciphertext.tag (5 partes) |
| Algoritmos no cabeçalho | alg (por exemplo, ES256, RS256, HS256, Ed25519) | alg para o gerenciamento de chaves (ECDH-ES, ECDH-ES+A256KW, RSA-OAEP-256, A256KW, dir) mais enc para o conteúdo (A256GCM, A128CBC-HS256) |
| Payload visível | Sim, apenas em base64url | Não |
| Remetente autenticado | Sim (assinatura ou MAC) | Apenas com chave simétrica ou com acordo de chaves autenticado; não com ECDH-ES ou RSA-OAEP sozinhos |
| Tamanho para um conjunto de claims de 70 bytes (medido abaixo) | 186 caracteres (ES256) | 405 caracteres (ECDH-ES+A256KW com A256GCM) |
| Usos típicos | Tokens de acesso e tokens de ID JWT, DPoP, requisições assinadas, SD-JWT | Tokens de ID criptografados, tokens armazenados no lado do cliente, algumas respostas FAPI e EUDI |
O que cada um protege
Um JWS assina base64url(header) + "." + base64url(payload). Qualquer pessoa que tenha o token pode decodificar e ler as claims; o depurador de JWT faz exatamente isso. A assinatura prova que o token veio do detentor da chave e não foi alterado, e é disso que uma API precisa para confiar nas claims.
Um JWE gera uma chave de criptografia de conteúdo (CEK) aleatória, criptografa o payload com o AEAD indicado em enc e entrega a CEK ao destinatário com o algoritmo de gerenciamento de chaves indicado em alg: encapsulada com RSA-OAEP, encapsulada com AES Key Wrap depois de um acordo de chaves ECDH-ES, derivada diretamente do ECDH-ES ou pré-compartilhada (dir). O cabeçalho protegido é autenticado como dados associados. Só o destinatário consegue ler o payload.
A assimetria importante: com gerenciamento de chaves por chave pública, qualquer pessoa que tenha a chave pública do destinatário pode criar um JWE válido. Descriptografar com sucesso não prova nada sobre o remetente. Por isso, tokens sensíveis que também precisam ser confiáveis são aninhados: um JWT assinado (JWS) colocado dentro de um JWE com cty: JWT e verificado depois da descriptografia.
Tamanhos na prática
O script abaixo, executado com o jose 6.2.13 no Node.js 26.10, protege as mesmas claims das duas formas.
import { CompactEncrypt, CompactSign, compactDecrypt, compactVerify, generateKeyPair } from "jose";
const claims = new TextEncoder().encode(JSON.stringify({ sub: "user-42", email: "alice@example.com", plan: "pro" }));
// JWS: integrity and origin; the payload is only base64url-encoded.
const signer = await generateKeyPair("ES256");
const jws = await new CompactSign(claims).setProtectedHeader({ alg: "ES256" }).sign(signer.privateKey);
await compactVerify(jws, signer.publicKey);
// JWE: confidentiality for the holder of the recipient's private key.
const recipient = await generateKeyPair("ECDH-ES+A256KW");
const jwe = await new CompactEncrypt(claims).setProtectedHeader({ alg: "ECDH-ES+A256KW", enc: "A256GCM" }).encrypt(recipient.publicKey);
const { plaintext } = await compactDecrypt(jwe, recipient.privateKey);
console.log("JWS parts:", jws.split(".").length, "length:", jws.length, "payload readable:", Buffer.from(jws.split(".")[1], "base64url").toString().includes("alice"));
console.log("JWE parts:", jwe.split(".").length, "length:", jwe.length, "decrypts:", new TextDecoder().decode(plaintext).includes("alice"));
JWS parts: 3 length: 186 payload readable: true
JWE parts: 5 length: 405 decrypts: true
A maior parte do tamanho adicional do JWE vem da chave pública efêmera (epk) no cabeçalho, da chave encapsulada, do IV de 12 bytes e da tag de 16 bytes. Um token aninhado, assinado e depois criptografado, cresce ainda mais, já que o JWS inteiro vira o texto claro do JWE.
Armadilhas
- Tratar as claims de um JWS como secretas. Não coloque dados pessoais, identificadores internos que você não publicaria nem segredos em um token apenas assinado; ele vai acabar em logs, no armazenamento do navegador e em tickets de suporte.
- Confiar em um JWE não assinado. A descriptografia com ECDH-ES ou RSA-OAEP prova apenas que o token foi criptografado para você.
- Algoritmos de JWE fracos ou legados. O
RSA1_5é vulnerável a ataques de padding oracle, oPBES2com uma contagem de iteraçõesp2cenorme pode ser usado para negação de serviço, e implementações deECDH-ESque aceitavam pontos fora da curva vazaram chaves privadas em 2017 (RFC 8725 §2.5). PrefiraECDH-ES+A256KWouRSA-OAEP-256comA256GCMe limite as iterações do PBES2 se precisar oferecer suporte a ele. - Escolha do algoritmo pelo token. Assim como no JWS, convém que o destinatário aceite apenas os valores de
algeencque espera para cada chave. - Compressão. O
zip: DEFdo JWE comprime antes de criptografar, o que pode vazar informações sobre o texto claro pelo comprimento (RFC 8725 §3.6); evite-o.
Quando escolher cada um
- Tokens de acesso verificados pelas suas APIs: JWS; mantenha as claims no mínimo ou use tokens opacos com introspecção se o conteúdo não puder ficar legível.
- Tokens de ID com dados pessoais armazenados por um navegador ou aplicativo móvel: o JWS é o usual; JWE aninhado se o seu modelo de ameaças incluir o ambiente do cliente.
- Tokens que passam por intermediários não confiáveis (redirecionamentos por terceiros, tokens embutidos em URLs ou QR codes): JWS aninhado dentro de um JWE.
- Mensagens entre máquinas sobre TLS com autenticação mútua: muitas vezes, nenhum dos dois; o TLS já oferece confidencialidade e integridade em trânsito, e um JWS só agrega valor se a mensagem precisar continuar verificável depois de armazenada ou encaminhada.
- Payloads com criptografia de ponta a ponta para um destinatário conhecido: JWE com
ECDH-ES+A256KWeA256GCM, ou formatos baseados em HPKE, à medida que forem padronizados para o JOSE.
Notas de migração
- Classifique as claims dos tokens JWS existentes: tudo o que não pode ficar visível para o portador sai do token ou passa para um JWE.
- Introduza os tokens aninhados aos poucos: emita JWEs com
cty: JWTpara os clientes que conseguem descriptografá-los e mantenha o JWS interno inalterado, para que a lógica de verificação seja reaproveitada. - Distribua as chaves de criptografia separadamente das chaves de assinatura: convém que as entradas do JWKS tenham
use: encouuse: sige valores dekiddistintos. - Fixe os algoritmos dos dois lados e rejeite
RSA1_5,PBES2sem limite de iterações e compressão. - Revise os logs: depois que os tokens passarem a ser criptografados, garanta que as claims descriptografadas não sejam registradas em logs onde o token nunca foi registrado. O inspetor de JWE mostra o cabeçalho e a estrutura de um JWE sem descriptografá-lo.