Hinter vielen Fehlern der Art „unsupported key format“ steckt die Frage PKCS#1 vs. PKCS#8. Meist tritt sie auf, wenn ein vor Jahren mit openssl genrsa erzeugter Schlüssel auf eine moderne Bibliothek trifft. Eine PKCS#1-Datei (-----BEGIN RSA PRIVATE KEY-----) enthält nur eine RSA-Schlüsselstruktur; eine PKCS#8-Datei (-----BEGIN PRIVATE KEY-----) verpackt den privaten Schlüssel eines beliebigen Algorithmus zusammen mit einem Algorithmusbezeichner, und ihre verschlüsselte Form (-----BEGIN ENCRYPTED PRIVATE KEY-----) ergänzt einen standardisierten Passwortschutz.
Auf einen Blick
PKCS#1 RSAPrivateKey | PKCS#8 PrivateKeyInfo / OneAsymmetricKey | |
|---|---|---|
| Spezifikation | RFC 8017, Anhang A.1.2 | RFC 5208, RFC 5958 |
| PEM-Label | RSA PRIVATE KEY | PRIVATE KEY; verschlüsselt: ENCRYPTED PRIVATE KEY |
| Algorithmen | Nur RSA | Beliebige: RSA, EC, Ed25519, X25519, ML-DSA, ML-KEM … |
| Algorithmusbezeichner | Keiner (ergibt sich aus dem Label) | rsaEncryption, id-ecPublicKey, 1.3.101.112 … |
| Verschlüsselung | OpenSSL-spezifische PEM-Header (Proc-Type, DEK-Info), schwache KDF | Standardisiertes PBES2 (PBKDF2 plus AES) nach RFC 8018 |
| Ausgabe von OpenSSL 3 | Nur mit -traditional | Standard bei genpkey, pkey, rsa |
Java PKCS8EncodedKeySpec | Nicht akzeptiert | Akzeptiert |
| Go | x509.ParsePKCS1PrivateKey | x509.ParsePKCS8PrivateKey |
Node.js createPrivateKey | Akzeptiert (PEM-Label wird erkannt) | Akzeptiert |
WebCrypto importKey | Nicht akzeptiert | Format pkcs8 |
Die Gegenstücke bei öffentlichen Schlüsseln sind PKCS#1 RSAPublicKey (BEGIN RSA PUBLIC KEY) und SubjectPublicKeyInfo (BEGIN PUBLIC KEY); alles andere erwartet die zweite Form.
Was in den Formaten steckt
Ein privater RSA-Schlüssel nach PKCS#1 ist eine DER-kodierte SEQUENCE aus Version, Modul n, öffentlichem Exponenten e, privatem Exponenten d, den Primzahlen p und q sowie den CRT-Werten dP, dQ und qInv. Weil die Struktur nichts über den Algorithmus aussagt, muss das PEM-Label es tun. Für Schlüssel auf elliptischen Kurven gibt es keine PKCS#1-Form (das analoge OpenSSL-Format BEGIN EC PRIVATE KEY stammt aus SEC 1, RFC 5915).
PKCS#8 verpackt einen algorithmusspezifischen Schlüssel in eine PrivateKeyInfo: eine Version, einen AlgorithmIdentifier und einen OCTET STRING mit dem inneren Schlüssel, der bei RSA genau die PKCS#1-Struktur ist. RFC 5958 (Version 2, OneAsymmetricKey) kann zusätzlich den öffentlichen Schlüssel transportieren. EncryptedPrivateKeyInfo ergänzt einen Verschlüsselungsalgorithmus und das Chiffrat; OpenSSL 3.6 verwendet PBES2 mit PBKDF2-HMAC-SHA256 und AES-256-CBC, wenn du Verschlüsselung anforderst.
Konvertieren
Das folgende Skript, ausgeführt mit OpenSSL 3.6.5, zeigt die Voreinstellungen und eine verlustfreie Konvertierung hin und zurück.
#!/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
Dieselben Befehle konvertieren EC-Schlüssel nach SEC 1 (BEGIN EC PRIVATE KEY) in PKCS#8. Private OpenSSH-Schlüssel (BEGIN OPENSSH PRIVATE KEY) sind ein drittes Format; ssh-keygen -p -m PKCS8 -f key schreibt einen solchen Schlüssel direkt in seiner Datei als PKCS#8 um.
Fehler, die dir begegnen
Java liest am strengsten: PKCS8EncodedKeySpec akzeptiert nur PKCS#8, und der Wortlaut der Fehlermeldung hat sich zwischen den Releases geändert. Das folgende Programm lädt je eine PEM-Datei jeder Art, und die Befehle danach führen es mit Java 17, 21 und 25 in den offiziellen Images von Eclipse Temurin aus.
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
Ältere Antworten im Netz zitieren den Text von Java 17; unter Java 21 und 25 lautet die Meldung auf oberster Ebene Unable to decode key, und „algid parse error, not a sequence“ steht nur noch in der Ursache. Durchsuche Logs nach beiden Texten.
Die anderen Laufzeitumgebungen, am selben Tag mit von OpenSSL 3.6.5 erzeugten Schlüsseln nachgestellt:
- Go:
x509.ParsePKCS8PrivateKeygibtx509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)zurück (Go 1.25) und sagt damit genau, was passiert ist. - Node.js 26 (
createPrivateKey, OpenSSL 3.6.5): PKCS#1-DER, das alstype: "pkcs8"deklariert ist, scheitert miterror:068000A8:asn1 encoding routines::wrong tag(CodeERR_OSSL_ASN1_WRONG_TAG); ein PKCS#1-Inhalt unter einem LabelBEGIN PRIVATE KEYscheitert miterror:1E08010C:DECODER routines::unsupported; dieselben PKCS#1-Bytes ergeben, alsspkiancreatePublicKeyübergeben,Failed to read asymmetric key. Ein verschlüsselter Schlüssel, der ohne Passphrase geladen wird, scheitert miterror:07880109:common libcrypto routines::interrupted or cancelled. - Mit veralteten Algorithmen verschlüsselte Schlüssel: Ein traditioneller PEM-Schlüssel, der mit DES verschlüsselt ist, oder ein PKCS#8-Schlüssel unter PBES1 mit MD5 und DES lässt sich in OpenSSL 3 nur mit dem Legacy-Provider laden. Ohne ihn gibt
openssl pkeydie Meldungerror:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupportedaus, gefolgt von dem Algorithmus, den es nicht abrufen konnte (DES-CBCoder bei PBES1PBKDF1), und Node.js meldeterror:0308010C:digital envelope routines::unsupported(ERR_OSSL_EVP_UNSUPPORTED). - Python (
cryptography46):load_pem_private_keyliest PKCS#1 und PKCS#8 gleichermaßen und löst bei einem verschlüsselten Schlüssel ohne PasswortTypeError: Password was not given but private key is encryptedaus. - WebCrypto:
importKey("pkcs8", …)mit PKCS#1-Bytes wird mit einemDataErrorabgelehnt; Node.js 26 formuliert ihn alsInvalid keyData, Browser verwenden eigene Texte.
Im Fehlerkatalog gibt es zu jedem dieser Fehler eine eigene Seite; die Links stehen unten.
Dieselbe Zweiteilung bei öffentlichen Schlüsseln
Öffentliche Schlüssel gibt es in denselben zwei Formen. PKCS#1 RSAPublicKey (BEGIN RSA PUBLIC KEY) enthält nur Modul und Exponent; SubjectPublicKeyInfo (BEGIN PUBLIC KEY, RFC 5280) verpackt den Schlüssel mit einem Algorithmusbezeichner, funktioniert für jeden Algorithmus und wird von Zertifikaten, JWK-Konvertern, X509EncodedKeySpec in Java, ParsePKIXPublicKey in Go und dem Import im Format spki bei WebCrypto erwartet. openssl rsa -pubin -in key.pem -RSAPublicKey_out und openssl pkey -pubin -in key.pem -pubout konvertieren zwischen beiden Formen. Die Verwechslung gibt es in beide Richtungen: Tools, die SPKI schreiben, benennen die Datei oft nach dem Muster id_rsa.pub, und die einzeiligen öffentlichen Schlüssel von OpenSSH selbst (ssh-rsa AAAA…) sind noch ein weiteres Format, das sich mit ssh-keygen -e -m PKCS8 konvertieren lässt.
Wann welches Format passt
- Neue Schlüssel, beliebiger Algorithmus: PKCS#8 (verschlüsselt auf dem Datenträger, unverschlüsselt in einem Secrets-Manager, der selbst schon verschlüsselt).
- Java, Go, .NET, WebCrypto, Importe in Cloud-KMS: PKCS#8 (DER oder PEM).
- Alte Software, die nur
RSA PRIVATE KEYliest (manche Appliances, älterer Ruby- und PHP-Code, alte SSH-Tools): PKCS#1 über-traditional, idealerweise erst bei der Übergabe an diese Software. - Schlüssel plus Zertifikat für Windows, macOS oder Java-Keystores: PKCS#12, das intern PKCS#8-Schlüssel enthält.
- SSH-Schlüssel: das OpenSSH-Format für
sshselbst; PKCS#8, wenn ein anderes Tool denselben Schlüssel braucht.
Hinweise zur Migration
- Finde Dateien in PKCS#1 und SEC 1: mit
grep -l "BEGIN RSA PRIVATE KEY\|BEGIN EC PRIVATE KEY"in Konfigurations-Repositorys und Secret Stores. - Konvertiere einmalig mit
openssl pkcs8 -topk8 -nocrypt(oder-v2 aes-256-cbczum Verschlüsseln) und passe die Programme an, die die Schlüssel lesen; das Schlüsselmaterial und das zugehörige Zertifikat ändern sich nicht. - Verschlüssele alte verschlüsselte Schlüssel neu: Die traditionelle PEM-Verschlüsselung (Header
Proc-TypeundDEK-Info) leitet ihren Schlüssel mit einer einzigen MD5-Iteration ab, und mit DES oder RC2 verschlüsselte Schlüssel brauchen in beiden Formaten den Legacy-Provider von OpenSSL; verschlüssele sie mit PBES2 und AES-256 neu. - Stelle den Code auf die generischen Parser um (
ParsePKCS8PrivateKey,load_pem_private_key,createPrivateKey), damit neue Algorithmen, darunter Ed25519 und ML-DSA, ohne Codeänderungen funktionieren. - Prüfe das Ergebnis: Signiere mit dem konvertierten Schlüssel und prüfe die Signatur mit dem bestehenden Zertifikat; der Schlüsselgenerator und der ASN.1-Explorer zeigen die Struktur, ohne etwas hochzuladen.