Aller au contenu
ecdsa.com

JWK vs PEM : clés JSON ou clés ASN.1

Une JWK est une clé en JSON ; un PEM est une clé en DER encodé en base64. Les deux portent les mêmes nombres, et la conversion entre les deux formats est sans perte, à condition de respecter quelques détails.
Éléments comparésJWK (JSON Web Key)PEM

JWK vs PEM dépend de l’endroit où va une clé. Les JSON Web Keys (RFC 7517) sont le format que parlent JOSE, OAuth et WebCrypto ; les fichiers PEM (l’encodage texte de la RFC 7468 pour des structures DER telles que SubjectPublicKeyInfo et PKCS #8) sont ce qu’attendent OpenSSL, les serveurs TLS, les outils SSH, Java et la plupart des logiciels de PKI. Le contenu cryptographique est identique : le choix porte donc sur l’outillage et les métadonnées.

Vue d’ensemble

JWK (RFC 7517)PEM (RFC 7468)
EncodageObjet JSON dont les membres sont en base64urlbase64 du DER entre -----BEGIN …----- et -----END …-----
Structure de la clé publiquekty plus les membres propres au type (crv, x, y, n, e)SubjectPublicKeyInfo (BEGIN PUBLIC KEY)
Clé privéeLe même objet plus d (et les membres CRT de RSA)PKCS #8 (BEGIN PRIVATE KEY), ou les anciennes formes PKCS #1 et SEC 1
Métadonnéeskid, alg, use, key_ops, x5c, x5t#S256Aucune (seulement des libellés)
CollectionsJWK Set : tableau "keys"Plusieurs blocs PEM dans un même fichier
Taille d’une clé publique P-256 (mesurée)126 caractères de JSON178 caractères
Condensat servant d’identifiantEmpreinte JWK (RFC 7638)Empreinte SPKI SHA-256, empreinte du certificat
ConsommateursBibliothèques JOSE, jwk de WebCrypto, jwks_uri d’OIDC, convertisseurs COSEOpenSSL, nginx, Apache, X509EncodedKeySpec de Java, x509 de Go, cryptography de Python

Ce que contient chaque format

Une JWK écrit directement les nombres de la clé. Une clé publique EC se compose de kty: EC, de crv: P-256 et des coordonnées x et y sous forme de chaînes base64url de longueur fixe ; une clé RSA, de kty: RSA avec n et e ; les clés Ed25519 et X25519, de kty: OKP avec crv et x (RFC 8037) ; les clés privées ajoutent d. Des membres facultatifs lient la clé à un usage : alg (un seul algorithme), use (sig ou enc), kid et des références de certificats.

Une clé publique PEM est l’encodage DER d’une structure SubjectPublicKeyInfo : un identifiant d’algorithme (par exemple id-ecPublicKey avec l’OID de P-256) et une chaîne de bits qui contient le point. Les clés privées sont des structures PKCS #8 PrivateKeyInfo, éventuellement chiffrées. Un PEM ne porte aucune métadonnée en dehors de son libellé : l’algorithme avec lequel une clé peut être utilisée doit donc être configuré ailleurs.

Conversion entre les deux formats

La conversion est sans perte. Le script ci-dessous, exécuté avec Node.js 26.10, convertit une clé publique P-256 de PEM en JWK puis inversement, et calcule les deux types d’identifiant.

Node.js : de PEM à JWK et retour, empreinte JWK contre condensat 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);
Sortie
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 dernière ligne est la surprise habituelle : une empreinte JWK hache du JSON canonique, une empreinte SPKI hache du DER, si bien que la même clé a deux « empreintes » différentes. Les systèmes doivent s’accorder sur celle dont ils parlent : par exemple, le cnf.jkt de DPoP est une empreinte JWK, tandis que l’épinglage de certificat utilise des condensats SPKI.

Pièges

  • Remplissage et longueur. Les valeurs d’une JWK sont en base64url sans remplissage, et les coordonnées EC doivent conserver leur longueur complète (32 octets pour P-256, 66 pour P-521), octets nuls de tête compris. Les convertisseurs écrits à la main qui suppriment ces zéros produisent des clés qui échouent environ une fois sur 128 pour P-256 et P-384, puisque x et y commencent chacune par un octet nul environ une fois sur 256 ; pour P-521, dont l’octet de poids fort ne contient qu’un seul bit, environ trois clés sur quatre échouent ; Node signale des JWK invalides, et WebCrypto lève une erreur DataError.
  • Clés privées dans des emplacements publics. Une JWK avec d est une clé privée. La publier dans un JWKS ou la coller dans un outil expose la clé ; les outils de ce site refusent les JWK avec d là où seule une clé publique est nécessaire.
  • Mauvais libellé PEM. BEGIN RSA PUBLIC KEY (PKCS #1) et BEGIN PUBLIC KEY (SPKI) sont des structures différentes ; de nombreux convertisseurs JWK n’acceptent que SPKI.
  • Métadonnées perdues. Convertir une JWK en PEM fait disparaître kid et alg ; conservez-les dans la configuration pour que les vérificateurs continuent d’épingler l’algorithme.
  • Clés privées Ed25519. Le d d’une JWK est la graine de 32 octets ; les clés privées brutes de certaines bibliothèques font 64 octets (graine plus clé publique).
  • Les certificats ne sont pas des clés. Une JWK peut référencer une chaîne de certificats dans x5c, mais ces entrées sont des certificats DER en base64 standard, pas en base64url, et les vérificateurs doivent valider la chaîne avant de faire confiance à la clé qu’elle contient ; de même, un bloc PEM BEGIN CERTIFICATE contient une clé publique, mais l’extraire (openssl x509 -pubkey -noout) est une étape distincte de la décision de lui faire confiance.

Quand choisir l’un ou l’autre

  • Clés de vérification de jetons publiées par un émetteur : JWK Set à l’adresse jwks_uri.
  • Clés transmises à des bibliothèques JOSE ou à WebCrypto dans les navigateurs : JWK (WebCrypto accepte aussi SPKI et PKCS #8 en DER).
  • Clés et certificats de serveurs TLS, CSR, workflows OpenSSL : PEM.
  • Code Java, .NET et Go qui utilise les classes de clés standard : PEM ou DER (SPKI et PKCS #8), avec une prise en charge de JWK par les bibliothèques JOSE.
  • Fichiers de configuration et gestionnaires de secrets : l’un ou l’autre ; PEM est plus largement pris en charge par les outils, JWK conserve les métadonnées avec la clé.
  • Épinglage et identifiants de clé : choisissez une seule définition du condensat (empreinte JWK ou SPKI SHA-256) et documentez-la.

Notes de migration

  1. Générez les clés dans le magasin qui les conservera, puis exportez la partie publique au format dont chaque consommateur a besoin ; ne faites pas passer les clés privées d’un format à l’autre plus souvent que nécessaire.
  2. Convertissez avec des bibliothèques : node:crypto (createPublicKey et export), cryptography en Python avec jwcrypto ou joserfc, jose en JavaScript, Nimbus en Java, ou le convertisseur JWK, qui s’exécute localement.
  3. Définissez kid, alg et use explicitement lorsque vous créez des JWK pour un JWKS ; dérivez kid de l’empreinte JWK si vous voulez des identifiants stables.
  4. Validez après la conversion : signez avec la clé privée et vérifiez avec la clé publique convertie avant de publier.
  5. Gardez les deux représentations synchronisées pendant la rotation, car, sinon, un JWKS et une passerelle configurée en PEM peuvent ne pas s’accorder sur la clé courante.

Références

Tous les comparatifs