JWK vs PEM é uma questão de para onde a chave vai. As JSON Web Keys (RFC 7517) são a língua do JOSE, do OAuth e da WebCrypto; os arquivos PEM (a codificação em texto da RFC 7468 para estruturas DER como SubjectPublicKeyInfo e PKCS #8) são o que o OpenSSL, os servidores TLS, as ferramentas de SSH, o Java e a maioria dos softwares de PKI esperam. O conteúdo criptográfico é idêntico, então a escolha depende das ferramentas e dos metadados.
Visão geral
| JWK (RFC 7517) | PEM (RFC 7468) | |
|---|---|---|
| Codificação | Objeto JSON com membros em base64url | base64 do DER entre -----BEGIN …----- e -----END …----- |
| Estrutura da chave pública | kty mais os membros específicos do tipo (crv, x, y, n, e) | SubjectPublicKeyInfo (BEGIN PUBLIC KEY) |
| Chave privada | O mesmo objeto mais d (e os membros CRT do RSA) | PKCS #8 (BEGIN PRIVATE KEY) ou as formas legadas PKCS #1 e SEC 1 |
| Metadados | kid, alg, use, key_ops, x5c, x5t#S256 | Nenhum (apenas rótulos) |
| Coleções | JWK Set: array "keys" | Vários blocos PEM em um único arquivo |
| Tamanho de uma chave pública P-256 (medido) | 126 caracteres de JSON | 178 caracteres |
| Hash de identificação | Impressão digital (thumbprint) da JWK (RFC 7638) | Impressão digital SHA-256 do SPKI, impressão digital do certificado |
| Consumidores | Bibliotecas JOSE, jwk da WebCrypto, jwks_uri do OIDC, conversores de COSE | OpenSSL, nginx, Apache, X509EncodedKeySpec do Java, x509 do Go, cryptography do Python |
O que há dentro de cada um
Uma JWK expõe diretamente os números da chave. Uma chave pública EC é kty: EC, crv: P-256 e as coordenadas x e y como strings base64url de comprimento fixo; uma chave RSA é kty: RSA com n e e; as chaves Ed25519 e X25519 são kty: OKP com crv e x (RFC 8037); as chaves privadas acrescentam d. Membros opcionais vinculam a chave a uma finalidade: alg (um único algoritmo), use (sig ou enc), kid e referências a certificados.
Uma chave pública PEM é a codificação DER de um SubjectPublicKeyInfo: um identificador de algoritmo (por exemplo, id-ecPublicKey com o OID da curva P-256) e uma cadeia de bits que contém o ponto. As chaves privadas são estruturas PKCS #8 PrivateKeyInfo, opcionalmente criptografadas. O PEM não carrega nenhum metadado além do rótulo, então o algoritmo com que uma chave pode ser usada precisa ser configurado em outro lugar.
Conversão entre os dois
A conversão não perde informação. O script abaixo, executado com o Node.js 26.10, converte uma chave pública P-256 de PEM para JWK e de volta para PEM e calcula os dois tipos de identificador.
import { createHash, createPublicKey, generateKeyPairSync } from "node:crypto";
const { publicKey } = generateKeyPairSync("ec", { namedCurve: "P-256" });
// PEM (SubjectPublicKeyInfo) -> JWK -> PEM again.
const pem = publicKey.export({ type: "spki", format: "pem" });
const jwk = publicKey.export({ format: "jwk" });
const roundTrip = createPublicKey({ key: jwk, format: "jwk" }).export({ type: "spki", format: "pem" });
// JWK thumbprint (RFC 7638): SHA-256 over the required members in lexicographic order.
const canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
const thumbprint = createHash("sha256").update(canonical).digest("base64url");
// SPKI fingerprint: SHA-256 over the DER-encoded SubjectPublicKeyInfo.
const spki = createHash("sha256").update(publicKey.export({ type: "spki", format: "der" })).digest("base64url");
console.log("JWK members:", Object.keys(jwk).join(", "));
console.log("PEM length:", pem.length, "JWK JSON length:", JSON.stringify(jwk).length);
console.log("PEM -> JWK -> PEM identical:", pem === roundTrip);
console.log("JWK thumbprint equals SPKI hash:", thumbprint === spki);
JWK members: kty, x, y, crv
PEM length: 178 JWK JSON length: 126
PEM -> JWK -> PEM identical: true
JWK thumbprint equals SPKI hash: false
A última linha é a surpresa mais comum: a impressão digital (thumbprint) da JWK é um hash do JSON canônico, e a impressão digital SPKI é um hash do DER, então a mesma chave tem duas “impressões digitais” diferentes. Os sistemas precisam concordar sobre qual das duas estão usando: por exemplo, o cnf.jkt do DPoP é uma impressão digital da JWK, enquanto a fixação de certificados usa hashes SPKI.
Armadilhas
- Padding e comprimento. Os valores de uma JWK são base64url sem padding, e as coordenadas EC precisam manter o comprimento completo (32 bytes para P-256, 66 para P-521), incluindo os bytes zero à esquerda. Conversores escritos à mão que removem esses zeros geram chaves que falham cerca de uma vez a cada 128 casos nas curvas P-256 e P-384, já que
xeycomeçam, cada uma, com um byte zero cerca de uma vez a cada 256; na P-521, cujo byte mais significativo contém um único bit, cerca de três chaves em cada quatro falham; o Node informa que a JWK é inválida, e a WebCrypto lança umDataError. - Chaves privadas em lugares públicos. Uma JWK com
dé uma chave privada. Publicá-la em um JWKS ou colá-la em uma ferramenta expõe a chave; as ferramentas deste site recusam JWKs comdonde só é necessária uma chave pública. - Rótulo PEM errado.
BEGIN RSA PUBLIC KEY(PKCS #1) eBEGIN PUBLIC KEY(SPKI) são estruturas diferentes; muitos conversores de JWK aceitam apenas SPKI. - Metadados perdidos. Converter uma JWK para PEM descarta
kidealg; mantenha esses valores na configuração para que os verificadores continuem fixando o algoritmo. - Chaves privadas Ed25519. O
dda JWK é a semente de 32 bytes; as chaves privadas brutas de algumas bibliotecas têm 64 bytes (semente mais chave pública). - Certificados não são chaves. Uma JWK pode referenciar uma cadeia de certificados em
x5c, mas essas entradas são certificados DER em base64 padrão, não em base64url, e os verificadores precisam validar a cadeia antes de confiar na chave contida no certificado; da mesma forma, um bloco PEMBEGIN CERTIFICATEcontém uma chave pública, mas extraí-la (openssl x509 -pubkey -noout) é uma etapa separada de confiar nela.
Quando escolher cada um
- Chaves de verificação de tokens publicadas por um emissor: JWK Set em
jwks_uri. - Chaves entregues a bibliotecas JOSE ou à WebCrypto nos navegadores: JWK (a WebCrypto também aceita SPKI e PKCS #8 em DER).
- Chaves e certificados de servidores TLS, CSRs, fluxos de trabalho com OpenSSL: PEM.
- Código Java, .NET e Go que usa as classes de chave padrão: PEM ou DER (SPKI e PKCS #8), com suporte a JWK por meio de bibliotecas JOSE.
- Arquivos de configuração e gerenciadores de segredos: qualquer um dos dois; o PEM é aceito por mais ferramentas, e a JWK mantém os metadados junto com a chave.
- Fixação e identificadores de chave: escolha uma única definição de hash (impressão digital da JWK ou SHA-256 do SPKI) e documente-a.
Notas de migração
- Gere as chaves no repositório de chaves que vai guardá-las e depois exporte a parte pública no formato de que cada consumidor precisa; não converta chaves privadas entre formatos mais vezes do que o necessário.
- Converta com bibliotecas:
node:crypto(createPublicKeyeexport),cryptographydo Python comjwcryptooujoserfc,joseem JavaScript, Nimbus em Java ou o conversor de JWK, que roda localmente. - Defina
kid,algeuseexplicitamente ao criar JWKs para um JWKS; derive okidda impressão digital da JWK se quiser identificadores estáveis. - Valide depois da conversão: assine com a chave privada e verifique com a chave pública convertida antes de publicar.
- Mantenha as duas representações sincronizadas durante a rotação, já que, do contrário, um JWKS e um gateway configurado com PEM podem divergir sobre qual é a chave atual.