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ón | Objeto JSON con miembros en base64url | base64 de DER entre -----BEGIN …----- y -----END …----- |
| Estructura de la clave pública | kty más los miembros propios del tipo (crv, x, y, n, e) | SubjectPublicKeyInfo (BEGIN PUBLIC KEY) |
| Clave privada | El 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 |
| Metadatos | kid, alg, use, key_ops, x5c, x5t#S256 | Ninguno (solo etiquetas) |
| Colecciones | JWK Set: array "keys" | Varios bloques PEM en un archivo |
| Tamaño de una clave pública P-256 (medido) | 126 caracteres de JSON | 178 caracteres |
| Hash identificador | Huella JWK (JWK thumbprint, RFC 7638) | Huella SHA-256 de SPKI, huella del certificado |
| Consumidores | Bibliotecas JOSE, jwk de WebCrypto, jwks_uri de OIDC, conversores COSE | OpenSSL, 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.
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
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
xeyempiezan 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 unDataError. - Claves privadas en lugares públicos. Una JWK con
des una clave privada. Publicarla en un JWKS o pegarla en una herramienta expone la clave; las herramientas de este sitio rechazan las JWK conddonde solo hace falta una clave pública. - Etiqueta PEM equivocada.
BEGIN RSA PUBLIC KEY(PKCS #1) yBEGIN PUBLIC KEY(SPKI) son estructuras distintas; muchos conversores de JWK solo aceptan SPKI. - Metadatos perdidos. Al convertir una JWK a PEM se pierden
kidyalg; guárdalos en la configuración para que los verificadores sigan fijando el algoritmo. - Claves privadas Ed25519. El
dde 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 PEMBEGIN CERTIFICATEcontiene 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
- 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.
- Convierte con bibliotecas:
node:crypto(createPublicKeyyexport),cryptographyde Python conjwcryptoojoserfc,joseen JavaScript, Nimbus en Java o el conversor de JWK, que se ejecuta en local. - Establece
kid,algyusede forma explícita al crear JWK para un JWKS; derivakidde la huella JWK si quieres identificadores estables. - Valida después de convertir: firma con la clave privada y verifica con la clave pública convertida antes de publicarla.
- 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.