Aller au contenu
ecdsa.com

PKCS#1 vs PKCS#8 : BEGIN RSA PRIVATE KEY ou PRIVATE KEY

Les deux contiennent les mêmes nombres RSA. PKCS#8 ajoute un identifiant d’algorithme et un chiffrement facultatif, ce qui explique que toutes les bibliothèques modernes le préfèrent et que certaines refusent tout le reste.
Éléments comparésFormat de clé PKCS #1PKCS #8

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 RSAPrivateKeyPKCS#8 PrivateKeyInfo / OneAsymmetricKey
SpécificationRFC 8017, annexe A.1.2RFC 5208, RFC 5958
Libellé PEMRSA PRIVATE KEYPRIVATE KEY ; forme chiffrée : ENCRYPTED PRIVATE KEY
AlgorithmesRSA uniquementTous : RSA, EC, Ed25519, X25519, ML-DSA, ML-KEM…
Identifiant d’algorithmeAucun (implicite dans le libellé)rsaEncryption, id-ecPublicKey, 1.3.101.112…
ChiffrementEn-têtes PEM propres à OpenSSL (Proc-Type, DEK-Info), KDF faiblePBES2 standard (PBKDF2 plus AES) selon la RFC 8018
Sortie d’OpenSSL 3Uniquement avec -traditionalPar défaut pour genpkey, pkey, rsa
PKCS8EncodedKeySpec de JavaRefuséAccepté
Gox509.ParsePKCS1PrivateKeyx509.ParsePKCS8PrivateKey
createPrivateKey de Node.jsAccepté (libellé PEM détecté)Accepté
importKey de WebCryptoRefusé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.

OpenSSL 3.6 : PKCS#8, PKCS#1 et PKCS#8 chiffré
#!/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"
Sortie
-----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.

LoadKey.java : charger des clés privées PEM avec PKCS8EncodedKeySpec
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);
      }
    }
  }
}
Exécution sur Java 17, 21 et 25 avec une clé PKCS#8 et une clé PKCS#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
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
Sortie (20 octobre 2026)
== 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.ParsePKCS8PrivateKey renvoie x509: 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é avec type: "pkcs8" échoue avec error:068000A8:asn1 encoding routines::wrong tag (code ERR_OSSL_ASN1_WRONG_TAG) ; un corps PKCS#1 sous un libellé BEGIN PRIVATE KEY échoue avec error:1E08010C:DECODER routines::unsupported ; les mêmes octets PKCS#1 passés à createPublicKey en tant que spki donnent Failed to read asymmetric key. Une clé chiffrée chargée sans phrase de passe échoue avec error: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 pkey affiche error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported suivi de l’algorithme qu’il n’a pas pu charger (DES-CBC, ou PBKDF1 pour PBES1), et Node.js signale error:0308010C:digital envelope routines::unsupported (ERR_OSSL_EVP_UNSUPPORTED).
  • Python (cryptography 46) : load_pem_private_key lit indifféremment PKCS#1 et PKCS#8, et lève TypeError: Password was not given but private key is encrypted pour une clé chiffrée fournie sans mot de passe.
  • WebCrypto : importKey("pkcs8", …) avec des octets PKCS#1 est rejeté avec une DataError ; Node.js 26 la formule Invalid 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 ssh lui-même ; PKCS#8 quand un autre outil a besoin de la même clé.

Notes de migration

  1. 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.
  2. Convertissez une fois pour toutes avec openssl pkcs8 -topk8 -nocrypt (ou -v2 aes-256-cbc pour chiffrer) et mettez à jour les consommateurs ; le matériel de clé et le certificat correspondant ne changent pas.
  3. Rechiffrez les anciennes clés chiffrées : le chiffrement PEM traditionnel (en-têtes Proc-Type et DEK-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.
  4. 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.
  5. 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.

Références

Tous les comparatifs