PKCS#1 vs PKCS#8, c’est la question qui se cache derrière bien des erreurs « unsupported key format », rencontrées le plus souvent quand une clé générée il y a des années avec openssl genrsa se retrouve face à une bibliothèque moderne. Un fichier PKCS#1 (-----BEGIN RSA PRIVATE KEY-----) ne contient qu’une structure de clé RSA ; un fichier PKCS#8 (-----BEGIN PRIVATE KEY-----) enveloppe la clé privée de n’importe quel algorithme avec un identifiant d’algorithme, et sa forme chiffrée (-----BEGIN ENCRYPTED PRIVATE KEY-----) y ajoute une protection standard par mot de passe.
Vue d’ensemble
PKCS#1 RSAPrivateKey | PKCS#8 PrivateKeyInfo / OneAsymmetricKey | |
|---|---|---|
| Spécification | RFC 8017, annexe A.1.2 | RFC 5208, RFC 5958 |
| Libellé PEM | RSA PRIVATE KEY | PRIVATE KEY ; forme chiffrée : ENCRYPTED PRIVATE KEY |
| Algorithmes | RSA uniquement | Tous : RSA, EC, Ed25519, X25519, ML-DSA, ML-KEM… |
| Identifiant d’algorithme | Aucun (implicite dans le libellé) | rsaEncryption, id-ecPublicKey, 1.3.101.112… |
| Chiffrement | En-têtes PEM propres à OpenSSL (Proc-Type, DEK-Info), KDF faible | PBES2 standard (PBKDF2 plus AES) selon la RFC 8018 |
| Sortie d’OpenSSL 3 | Uniquement avec -traditional | Par défaut pour genpkey, pkey, rsa |
PKCS8EncodedKeySpec de Java | Refusé | Accepté |
| Go | x509.ParsePKCS1PrivateKey | x509.ParsePKCS8PrivateKey |
createPrivateKey de Node.js | Accepté (libellé PEM détecté) | Accepté |
importKey de WebCrypto | Refusé | Format pkcs8 |
Leurs équivalents pour les clés publiques sont PKCS#1 RSAPublicKey (BEGIN RSA PUBLIC KEY) et SubjectPublicKeyInfo (BEGIN PUBLIC KEY), ce dernier étant ce qu’attend tout le reste.
Ce qu’ils contiennent
Une clé privée RSA PKCS#1 est une SEQUENCE DER composée de la version, du module n, de l’exposant public e, de l’exposant privé d, des nombres premiers p et q, et des valeurs CRT dP, dQ et qInv. Comme la structure ne dit rien de l’algorithme, c’est au libellé PEM de l’indiquer ; il n’existe pas de forme PKCS#1 pour les clés à courbe elliptique (le format analogue d’OpenSSL, BEGIN EC PRIVATE KEY, vient de SEC 1, RFC 5915).
PKCS#8 enveloppe une clé propre à un algorithme dans une structure PrivateKeyInfo : une version, un AlgorithmIdentifier et une OCTET STRING contenant la clé interne, qui, pour RSA, est exactement la structure PKCS#1. La RFC 5958 (version 2, OneAsymmetricKey) peut aussi transporter la clé publique. EncryptedPrivateKeyInfo ajoute un algorithme de chiffrement et le texte chiffré ; OpenSSL 3.6 utilise PBES2 avec PBKDF2-HMAC-SHA256 et AES-256-CBC lorsque vous demandez le chiffrement.
Conversion
Le script ci-dessous, exécuté avec OpenSSL 3.6.5, montre les valeurs par défaut et un aller-retour sans perte.
#!/bin/sh
dir=$(mktemp -d)
cd "$dir" || exit 1
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out pkcs8.pem 2>/dev/null
openssl pkey -in pkcs8.pem -traditional -out pkcs1.pem
openssl pkey -in pkcs8.pem -aes256 -passout pass:demo -out enc.pem
head -1 pkcs8.pem; head -1 pkcs1.pem; head -1 enc.pem
openssl pkcs8 -topk8 -nocrypt -in pkcs1.pem -out back.pem && head -1 back.pem
cmp -s pkcs8.pem back.pem && echo "round trip PKCS#1 -> PKCS#8: identical"
cd / && rm -rf "$dir"
-----BEGIN PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----
-----BEGIN ENCRYPTED PRIVATE KEY-----
-----BEGIN PRIVATE KEY-----
round trip PKCS#1 -> PKCS#8: identical
Les mêmes commandes convertissent en PKCS#8 les clés EC SEC 1 (BEGIN EC PRIVATE KEY). Les clés privées OpenSSH (BEGIN OPENSSH PRIVATE KEY) constituent un troisième format ; ssh-keygen -p -m PKCS8 -f key en réécrit une sur place au format PKCS#8.
Les erreurs que vous rencontrerez
Java est le lecteur le plus strict : PKCS8EncodedKeySpec n’accepte que PKCS#8, et la formulation de son erreur a changé d’une version à l’autre. Le programme ci-dessous charge un fichier PEM de chaque sorte, et les commandes qui le suivent l’exécutent sur Java 17, 21 et 25 dans les images officielles Eclipse Temurin.
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class LoadKey {
public static void main(String[] args) throws Exception {
System.out.println("== Java " + Runtime.version());
for (String file : args) {
String pem = Files.readString(Path.of(file));
byte[] der = Base64.getMimeDecoder().decode(pem.replaceAll("-----[A-Z ]+-----", ""));
try {
KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(der));
System.out.println(file + ": loaded");
} catch (Exception e) {
System.out.println(file + ": " + e);
for (Throwable t = e.getCause(); t != null; t = t.getCause()) System.out.println(" Caused by: " + t);
}
}
}
}
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out pkcs8.pem 2>/dev/null
openssl pkey -in pkcs8.pem -traditional -out pkcs1.pem
for v in 17.0.20.1_1 21.0.12.1_1 25.0.4.1_1; do
docker run --rm -v "$PWD":/work:ro -w /work "eclipse-temurin:$v-jdk-noble" java LoadKey.java pkcs8.pem pkcs1.pem
done
== Java 17.0.20.1+1
pkcs8.pem: loaded
pkcs1.pem: java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
Caused by: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
== Java 21.0.12.1+1-LTS
pkcs8.pem: loaded
pkcs1.pem: java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: Unable to decode key
Caused by: java.security.InvalidKeyException: Unable to decode key
Caused by: java.io.IOException: algid parse error, not a sequence
== Java 25.0.4.1+1-LTS
pkcs8.pem: loaded
pkcs1.pem: java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: Unable to decode key
Caused by: java.security.InvalidKeyException: Unable to decode key
Caused by: java.io.IOException: algid parse error, not a sequence
Les réponses plus anciennes que l’on trouve sur le web citent le texte de Java 17 ; sur Java 21 et 25, le message de premier niveau est Unable to decode key, et « algid parse error, not a sequence » ne subsiste que dans la cause. Cherchez les deux dans les journaux.
Les autres environnements d’exécution, testés le même jour avec des clés générées par OpenSSL 3.6.5 :
- Go :
x509.ParsePKCS8PrivateKeyrenvoiex509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)(Go 1.25), ce qui indique exactement ce qui s’est passé. - Node.js 26 (
createPrivateKey, OpenSSL 3.6.5) : du DER PKCS#1 déclaré avectype: "pkcs8"échoue avecerror:068000A8:asn1 encoding routines::wrong tag(codeERR_OSSL_ASN1_WRONG_TAG) ; un corps PKCS#1 sous un libelléBEGIN PRIVATE KEYéchoue avecerror:1E08010C:DECODER routines::unsupported; les mêmes octets PKCS#1 passés àcreatePublicKeyen tant quespkidonnentFailed to read asymmetric key. Une clé chiffrée chargée sans phrase de passe échoue avecerror:07880109:common libcrypto routines::interrupted or cancelled. - Clés chiffrées avec d’anciens algorithmes : une clé PEM traditionnelle chiffrée avec DES, ou une clé PKCS#8 sous PBES1 avec MD5 et DES, ne se charge dans OpenSSL 3 qu’avec le fournisseur legacy. Sans lui,
openssl pkeyafficheerror:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupportedsuivi de l’algorithme qu’il n’a pas pu charger (DES-CBC, ouPBKDF1pour PBES1), et Node.js signaleerror:0308010C:digital envelope routines::unsupported(ERR_OSSL_EVP_UNSUPPORTED). - Python (
cryptography46) :load_pem_private_keylit indifféremment PKCS#1 et PKCS#8, et lèveTypeError: Password was not given but private key is encryptedpour une clé chiffrée fournie sans mot de passe. - WebCrypto :
importKey("pkcs8", …)avec des octets PKCS#1 est rejeté avec uneDataError; Node.js 26 la formuleInvalid keyData, les navigateurs utilisent leur propre texte.
Le catalogue d’erreurs consacre une page à chacune de ces erreurs, avec les liens ci-dessous.
La même distinction pour les clés publiques
Les clés publiques ont les deux mêmes formes. PKCS#1 RSAPublicKey (BEGIN RSA PUBLIC KEY) ne contient que le module et l’exposant ; SubjectPublicKeyInfo (BEGIN PUBLIC KEY, RFC 5280) enveloppe la clé avec un identifiant d’algorithme, fonctionne pour tous les algorithmes, et c’est ce qu’attendent les certificats, les convertisseurs JWK, X509EncodedKeySpec de Java, ParsePKIXPublicKey de Go et l’import spki de WebCrypto. openssl rsa -pubin -in key.pem -RSAPublicKey_out et openssl pkey -pubin -in key.pem -pubout convertissent de l’une à l’autre. La confusion va dans les deux sens : les outils qui écrivent du SPKI nomment souvent le fichier à la manière de id_rsa.pub, et les clés publiques sur une ligne propres à OpenSSH (ssh-rsa AAAA…) constituent encore un autre format, convertible avec ssh-keygen -e -m PKCS8.
Quand choisir l’un ou l’autre
- Nouvelles clés, quel que soit l’algorithme : PKCS#8 (chiffré sur disque, non chiffré dans un gestionnaire de secrets qui chiffre déjà).
- Java, Go, .NET, WebCrypto, imports dans un KMS cloud : PKCS#8 (DER ou PEM).
- Anciens logiciels qui ne lisent que
RSA PRIVATE KEY(certaines appliances, ancien code Ruby et PHP, anciens outils SSH) : PKCS#1 via-traditional, idéalement uniquement au moment de la livraison. - Clé plus certificat pour Windows, macOS ou les keystores Java : PKCS#12, qui contient en interne des clés PKCS#8.
- Clés SSH : le format OpenSSH pour
sshlui-même ; PKCS#8 quand un autre outil a besoin de la même clé.
Notes de migration
- Repérez les fichiers PKCS#1 et SEC 1 :
grep -l "BEGIN RSA PRIVATE KEY\|BEGIN EC PRIVATE KEY"dans l’ensemble des dépôts de configuration et des magasins de secrets. - Convertissez une fois pour toutes avec
openssl pkcs8 -topk8 -nocrypt(ou-v2 aes-256-cbcpour chiffrer) et mettez à jour les consommateurs ; le matériel de clé et le certificat correspondant ne changent pas. - Rechiffrez les anciennes clés chiffrées : le chiffrement PEM traditionnel (en-têtes
Proc-TypeetDEK-Info) dérive sa clé avec une seule itération de MD5, et les clés chiffrées avec DES ou RC2, dans l’un ou l’autre format, nécessitent le fournisseur legacy d’OpenSSL ; rechiffrez-les avec PBES2 et AES-256. - Passez le code aux fonctions d’analyse génériques (
ParsePKCS8PrivateKey,load_pem_private_key,createPrivateKey) pour que les nouveaux algorithmes, dont Ed25519 et ML-DSA, fonctionnent sans modifier le code. - Vérifiez en signant avec la clé convertie et en vérifiant la signature avec le certificat existant ; le générateur de clés et l’explorateur ASN.1 affichent la structure sans rien envoyer.