「サポートされていない鍵形式」というエラーの多くは、PKCS#1 と PKCS#8 の違いが原因です。たいていは、何年も前に openssl genrsa で生成した鍵を最近のライブラリで読み込もうとしたときに起こります。PKCS#1 のファイル(-----BEGIN RSA PRIVATE KEY-----)には RSA 鍵の構造しか入っていません。一方、PKCS#8 のファイル(-----BEGIN PRIVATE KEY-----)は、任意のアルゴリズムの秘密鍵をアルゴリズム識別子とともに包み、その暗号化形式(-----BEGIN ENCRYPTED PRIVATE KEY-----)は標準的なパスワード保護を加えます。
比較表
PKCS#1 RSAPrivateKey | PKCS#8 PrivateKeyInfo / OneAsymmetricKey | |
|---|---|---|
| 仕様 | RFC 8017 付録 A.1.2 | RFC 5208、RFC 5958 |
| PEM ラベル | RSA PRIVATE KEY | PRIVATE KEY。暗号化時は ENCRYPTED PRIVATE KEY |
| アルゴリズム | RSA のみ | 任意:RSA、EC、Ed25519、X25519、ML-DSA、ML-KEM … |
| アルゴリズム識別子 | なし(ラベルで暗示) | rsaEncryption、id-ecPublicKey、1.3.101.112 … |
| 暗号化 | OpenSSL 独自の PEM ヘッダー(Proc-Type、DEK-Info)、弱い KDF | RFC 8018 に基づく標準の PBES2(PBKDF2 と AES) |
| OpenSSL 3 の出力 | -traditional 指定時のみ | genpkey、pkey、rsa のデフォルト |
Java の PKCS8EncodedKeySpec | 非対応 | 対応 |
| Go | x509.ParsePKCS1PrivateKey | x509.ParsePKCS8PrivateKey |
Node.js の createPrivateKey | 対応(PEM ラベルで判別) | 対応 |
WebCrypto の importKey | 非対応 | pkcs8 形式 |
公開鍵でこれらに対応するのは PKCS#1 の RSAPublicKey(BEGIN RSA PUBLIC KEY)と SubjectPublicKeyInfo(BEGIN PUBLIC KEY)で、ほかのあらゆるソフトウェアが想定するのは後者です。
それぞれの中身
PKCS#1 の RSA 秘密鍵は、バージョン、モジュラス n、公開指数 e、秘密指数 d、素数 p と q、CRT の値 dP、dQ、qInv を並べた DER の SEQUENCE です。構造自体はアルゴリズムについて何も示さないため、PEM ラベルがそれを示さなければなりません。楕円曲線の鍵には PKCS#1 の形式はありません(OpenSSL でこれに相当する BEGIN EC PRIVATE KEY 形式は、SEC 1、RFC 5915 に由来します)。
PKCS#8 は、アルゴリズム固有の鍵を PrivateKeyInfo で包みます。中身はバージョン、AlgorithmIdentifier、内側の鍵を格納した OCTET STRING で、RSA の場合、この内側の鍵は PKCS#1 の構造そのものです。RFC 5958(バージョン 2、OneAsymmetricKey)では公開鍵も格納できます。EncryptedPrivateKeyInfo は暗号化アルゴリズムと暗号文を加えたもので、OpenSSL 3.6 で暗号化を指定すると、PBKDF2-HMAC-SHA256 と AES-256-CBC を使う PBES2 が使われます。
変換
次のスクリプトは OpenSSL 3.6.5 で実行したもので、デフォルトの形式と、情報を失わない往復変換を示します。
#!/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
同じコマンドで、SEC 1 の EC 鍵(BEGIN EC PRIVATE KEY)も PKCS#8 に変換できます。OpenSSH の秘密鍵(BEGIN OPENSSH PRIVATE KEY)は第 3 の形式で、ssh-keygen -p -m PKCS8 -f key を使うと、そのファイルを PKCS#8 としてその場で書き換えられます。
遭遇するエラー
最も厳格なのは Java です。PKCS8EncodedKeySpec は PKCS#8 しか受け付けず、エラーの文言はリリースによって変わっています。次のプログラムは各種類の PEM ファイルを 1 つずつ読み込み、その後のコマンドは、公式の Eclipse Temurin イメージを使ってこのプログラムを Java 17、21、25 で実行します。
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
Web 上の古い回答は Java 17 の文言を引用しています。Java 21 と 25 では最上位のメッセージは Unable to decode key で、「algid parse error, not a sequence」は原因の例外にしか残っていません。ログは両方の文言で検索してください。
ほかのランタイムの結果は次のとおりです(同じ日に、OpenSSL 3.6.5 で生成した鍵で再現しました)。
- Go:
x509.ParsePKCS8PrivateKeyはx509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)を返します(Go 1.25)。何が起きたのかがそのまま分かるメッセージです。 - Node.js 26(
createPrivateKey、OpenSSL 3.6.5):type: "pkcs8"と指定した PKCS#1 の DER はerror:068000A8:asn1 encoding routines::wrong tag(コードERR_OSSL_ASN1_WRONG_TAG)というエラーになります。BEGIN PRIVATE KEYラベルの下に PKCS#1 の本体を入れるとerror:1E08010C:DECODER routines::unsupportedになり、同じ PKCS#1 のバイト列をspkiとしてcreatePublicKeyに渡すとFailed to read asymmetric keyになります。暗号化された鍵をパスフレーズなしで読み込むとerror:07880109:common libcrypto routines::interrupted or cancelledになります。 - 旧来のアルゴリズムで暗号化された鍵:DES で暗号化した従来形式の PEM 鍵や、MD5 と DES による PBES1 で保護した PKCS#8 の鍵は、OpenSSL 3 ではレガシープロバイダーを使わないと読み込めません。レガシープロバイダーがないと、
openssl pkeyはerror:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupportedに続けて、取得できなかったアルゴリズム(DES-CBC、PBES1 の場合はPBKDF1)を出力し、Node.js はerror:0308010C:digital envelope routines::unsupported(ERR_OSSL_EVP_UNSUPPORTED)を報告します。 - Python(
cryptography46):load_pem_private_keyは PKCS#1 も PKCS#8 も同じように読み込み、暗号化された鍵をパスワードなしで読み込むとTypeError: Password was not given but private key is encryptedを送出します。 - WebCrypto:PKCS#1 のバイト列を渡した
importKey("pkcs8", …)はDataErrorで失敗します。Node.js 26 ではメッセージがInvalid keyDataになり、ブラウザーはそれぞれ独自の文言を使います。
エラーカタログにはそれぞれのエラーのページがあり、このページの下にリンクを掲載しています。
公開鍵にも同じ区別がある
公開鍵にも同じ 2 つの形があります。PKCS#1 の RSAPublicKey(BEGIN RSA PUBLIC KEY)はモジュラスと指数だけを保持します。SubjectPublicKeyInfo(BEGIN PUBLIC KEY、RFC 5280)は鍵をアルゴリズム識別子とともに包み、あらゆるアルゴリズムに対応します。証明書、JWK 変換ツール、Java の X509EncodedKeySpec、Go の ParsePKIXPublicKey、WebCrypto の spki インポートが想定するのもこちらです。両者の変換には openssl rsa -pubin -in key.pem -RSAPublicKey_out と openssl pkey -pubin -in key.pem -pubout を使います。混乱はどちらの方向にも起こります。SPKI を書き出すツールがファイルに id_rsa.pub のような名前を付けることは多く、OpenSSH 独自の 1 行形式の公開鍵(ssh-rsa AAAA…)はさらに別の形式で、ssh-keygen -e -m PKCS8 で変換できます。
どちらを選ぶべきか
- 新しい鍵(アルゴリズムを問わず):PKCS#8 を使います(ディスク上では暗号化し、それ自体が暗号化を行うシークレットマネージャーの中では暗号化しません)。
- Java、Go、.NET、WebCrypto、クラウド KMS へのインポート:PKCS#8(DER または PEM)を使います。
RSA PRIVATE KEYしか読めない古いソフトウェア(一部のアプライアンス、古い Ruby や PHP のコード、旧来の SSH ツール):-traditionalで PKCS#1 を出力します。できれば、受け渡しの時点に限ってください。- Windows、macOS、Java のキーストア向けの鍵と証明書:PKCS#12 を使います。PKCS#12 は内部に PKCS#8 の鍵を含みます。
- SSH 鍵:
ssh自体には OpenSSH 形式を、別のツールで同じ鍵が必要な場合は PKCS#8 を使います。
移行時の注意点
- PKCS#1 と SEC 1 のファイルを探す:設定リポジトリとシークレットストア全体に対して
grep -l "BEGIN RSA PRIVATE KEY\|BEGIN EC PRIVATE KEY"を実行します。 - 一度だけ変換する:
openssl pkcs8 -topk8 -nocrypt(暗号化する場合は-v2 aes-256-cbc)で変換し、鍵を読み込む側を更新します。鍵の中身も、対応する証明書も変わりません。 - 暗号化された古い鍵を暗号化し直す:従来形式の PEM の暗号化(
Proc-TypeとDEK-Infoヘッダー)は、MD5 を 1 回反復するだけで鍵を導出します。また、DES や RC2 で暗号化された鍵には、どちらの形式でも OpenSSL のレガシープロバイダーが必要です。PBES2 と AES-256 で暗号化し直してください。 - コードを汎用パーサーに切り替える(
ParsePKCS8PrivateKey、load_pem_private_key、createPrivateKey):これで、Ed25519 や ML-DSA を含む新しいアルゴリズムが、コードを変更せずに動作します。 - 検証する:変換した鍵で署名し、その署名を既存の証明書で検証します。鍵生成ツールと ASN.1 エクスプローラーを使えば、何も送信せずに構造を確認できます。