Saltar al contenido
ecdsa.com

JWK frente a PEM: claves en JSON o claves en ASN.1

Una JWK es una clave en JSON; un PEM es una clave en DER envuelto en base64. Contienen los mismos números, y la conversión entre ambos no pierde nada si respetas algunos detalles.
Qué se comparaJWK (JSON Web Key)PEM

JWK frente a PEM es una cuestión de adónde va la clave. Las JSON Web Keys (RFC 7517) son el lenguaje común de JOSE, OAuth y WebCrypto; los archivos PEM (la codificación de texto del RFC 7468 para estructuras DER como SubjectPublicKeyInfo y PKCS #8) son lo que esperan OpenSSL, los servidores TLS, las herramientas SSH, Java y la mayor parte del software de PKI. El contenido criptográfico es idéntico, así que la elección depende de las herramientas y de los metadatos.

De un vistazo

JWK (RFC 7517)PEM (RFC 7468)
CodificaciónObjeto JSON con miembros en base64urlbase64 de DER entre -----BEGIN …----- y -----END …-----
Estructura de la clave públicakty más los miembros propios del tipo (crv, x, y, n, e)SubjectPublicKeyInfo (BEGIN PUBLIC KEY)
Clave privadaEl mismo objeto más d (y los miembros CRT de RSA)PKCS #8 (BEGIN PRIVATE KEY), o las formas heredadas PKCS #1 y SEC 1
Metadatoskid, alg, use, key_ops, x5c, x5t#S256Ninguno (solo etiquetas)
ColeccionesJWK Set: array "keys"Varios bloques PEM en un archivo
Tamaño de una clave pública P-256 (medido)126 caracteres de JSON178 caracteres
Hash identificadorHuella JWK (JWK thumbprint, RFC 7638)Huella SHA-256 de SPKI, huella del certificado
ConsumidoresBibliotecas JOSE, jwk de WebCrypto, jwks_uri de OIDC, conversores COSEOpenSSL, nginx, Apache, X509EncodedKeySpec de Java, x509 de Go, cryptography de Python

Qué contiene cada uno

Una JWK expone directamente los números de la clave. Una clave pública EC es kty: EC, crv: P-256 y las coordenadas x e y como cadenas base64url de longitud fija; una clave RSA es kty: RSA con n y e; las claves Ed25519 y X25519 son kty: OKP con crv y x (RFC 8037); las claves privadas añaden d. Los miembros opcionales vinculan la clave a un propósito: alg (un único algoritmo), use (sig o enc), kid y referencias a certificados.

Una clave pública PEM es la codificación DER de una estructura SubjectPublicKeyInfo: un identificador de algoritmo (por ejemplo, id-ecPublicKey con el OID de P-256) y una cadena de bits que contiene el punto. Las claves privadas son estructuras PrivateKeyInfo de PKCS #8, opcionalmente cifradas. PEM no lleva más metadatos que su etiqueta, así que el algoritmo con el que se puede usar una clave debe configurarse en otro lugar.

Conversión entre ambos

La conversión no pierde información. El script siguiente, ejecutado con Node.js 26.10, convierte una clave pública P-256 de PEM a JWK y de vuelta a PEM, y calcula los dos tipos de identificador.

Node.js: de PEM a JWK y de vuelta, huella JWK frente a 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);
Salida
JWK members: kty, x, y, crv
PEM length: 178 JWK JSON length: 126
PEM -> JWK -> PEM identical: true
JWK thumbprint equals SPKI hash: false

La última línea es la sorpresa habitual: una huella JWK calcula el hash de un JSON canónico y una huella SPKI calcula el hash de DER, así que la misma clave tiene dos «huellas» distintas. Los sistemas deben ponerse de acuerdo sobre cuál de ellas usan: por ejemplo, el cnf.jkt de DPoP es una huella JWK, mientras que la fijación de certificados usa hashes SPKI.

Trampas habituales

  • Relleno y longitud. Los valores de una JWK son base64url sin relleno, y las coordenadas EC deben conservar su longitud completa (32 bytes en P-256, 66 en P-521), incluidos los bytes cero iniciales. Los conversores escritos a mano que eliminan esos ceros crean claves que fallan aproximadamente una vez de cada 128 en P-256 y P-384, ya que x e y empiezan cada una por un byte cero en torno a una vez de cada 256; en P-521, cuyo byte más significativo contiene un solo bit, fallan aproximadamente tres claves de cada cuatro; Node rechaza la JWK por no válida y WebCrypto lanza un DataError.
  • Claves privadas en lugares públicos. Una JWK con d es una clave privada. Publicarla en un JWKS o pegarla en una herramienta expone la clave; las herramientas de este sitio rechazan las JWK con d donde solo hace falta una clave pública.
  • Etiqueta PEM equivocada. BEGIN RSA PUBLIC KEY (PKCS #1) y BEGIN PUBLIC KEY (SPKI) son estructuras distintas; muchos conversores de JWK solo aceptan SPKI.
  • Metadatos perdidos. Al convertir una JWK a PEM se pierden kid y alg; guárdalos en la configuración para que los verificadores sigan fijando el algoritmo.
  • Claves privadas Ed25519. El d de una JWK es la semilla de 32 bytes; en algunas bibliotecas, la clave privada en bruto ocupa 64 bytes (la semilla más la clave pública).
  • Los certificados no son claves. Una JWK puede hacer referencia a una cadena de certificados en x5c, pero esas entradas son certificados DER en base64 estándar, no en base64url, y los verificadores deben validar la cadena antes de confiar en la clave que contiene; del mismo modo, un bloque PEM BEGIN CERTIFICATE contiene una clave pública, pero extraerla (openssl x509 -pubkey -noout) es un paso distinto de confiar en ella.

Cuándo elegir cada uno

  • Claves de verificación de tokens que publica un emisor: JWK Set en jwks_uri.
  • Claves que se entregan a bibliotecas JOSE o a WebCrypto en los navegadores: JWK (WebCrypto también acepta SPKI y PKCS #8 en DER).
  • Claves y certificados de servidores TLS, CSR, flujos de trabajo con OpenSSL: PEM.
  • Código Java, .NET y Go que usa las clases de clave estándar: PEM o DER (SPKI y PKCS #8), y JWK a través de bibliotecas JOSE.
  • Archivos de configuración y gestores de secretos: cualquiera de los dos; PEM lo admiten más herramientas y JWK guarda los metadatos junto a la clave.
  • Fijación e identificadores de clave: elige una sola definición de hash (huella JWK o SHA-256 de SPKI) y documéntala.

Notas de migración

  1. Genera las claves en el almacén que las va a guardar y después exporta la parte pública en el formato que necesite cada consumidor; no conviertas claves privadas de un formato a otro más veces de las necesarias.
  2. Convierte con bibliotecas: node:crypto (createPublicKey y export), cryptography de Python con jwcrypto o joserfc, jose en JavaScript, Nimbus en Java o el conversor de JWK, que se ejecuta en local.
  3. Establece kid, alg y use de forma explícita al crear JWK para un JWKS; deriva kid de la huella JWK si quieres identificadores estables.
  4. Valida después de convertir: firma con la clave privada y verifica con la clave pública convertida antes de publicarla.
  5. Mantén sincronizadas ambas representaciones durante la rotación, porque de lo contrario un JWKS y una pasarela configurada con PEM pueden discrepar sobre cuál es la clave actual.

Referencias

Todas las comparaciones