Pular para o conteúdo
ecdsa.com

JWK vs PEM: chaves em JSON ou chaves em ASN.1

Uma JWK é uma chave em JSON; um PEM é uma chave em DER envolto em base64. Os dois carregam os mesmos números, e a conversão entre eles não perde nada se você respeitar alguns detalhes.
Itens comparadosJWK (JSON Web Key)PEM

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çãoObjeto JSON com membros em base64urlbase64 do DER entre -----BEGIN …----- e -----END …-----
Estrutura da chave públicakty mais os membros específicos do tipo (crv, x, y, n, e)SubjectPublicKeyInfo (BEGIN PUBLIC KEY)
Chave privadaO mesmo objeto mais d (e os membros CRT do RSA)PKCS #8 (BEGIN PRIVATE KEY) ou as formas legadas PKCS #1 e SEC 1
Metadadoskid, alg, use, key_ops, x5c, x5t#S256Nenhum (apenas rótulos)
ColeçõesJWK Set: array "keys"Vários blocos PEM em um único arquivo
Tamanho de uma chave pública P-256 (medido)126 caracteres de JSON178 caracteres
Hash de identificaçãoImpressão digital (thumbprint) da JWK (RFC 7638)Impressão digital SHA-256 do SPKI, impressão digital do certificado
ConsumidoresBibliotecas JOSE, jwk da WebCrypto, jwks_uri do OIDC, conversores de COSEOpenSSL, 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.

Node.js: de PEM para JWK e de volta, thumbprint versus hash SPKI
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);
Saída
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 x e y começ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 um DataError.
  • 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 com d onde só é necessária uma chave pública.
  • Rótulo PEM errado. BEGIN RSA PUBLIC KEY (PKCS #1) e BEGIN PUBLIC KEY (SPKI) são estruturas diferentes; muitos conversores de JWK aceitam apenas SPKI.
  • Metadados perdidos. Converter uma JWK para PEM descarta kid e alg; mantenha esses valores na configuração para que os verificadores continuem fixando o algoritmo.
  • Chaves privadas Ed25519. O d da 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 PEM BEGIN CERTIFICATE conté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

  1. 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.
  2. Converta com bibliotecas: node:crypto (createPublicKey e export), cryptography do Python com jwcrypto ou joserfc, jose em JavaScript, Nimbus em Java ou o conversor de JWK, que roda localmente.
  3. Defina kid, alg e use explicitamente ao criar JWKs para um JWKS; derive o kid da impressão digital da JWK se quiser identificadores estáveis.
  4. Valide depois da conversão: assine com a chave privada e verifique com a chave pública convertida antes de publicar.
  5. 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.

Referências

Todas as comparações