Перейти к содержимому
ecdsa.com

PKCS#1 или PKCS#8: BEGIN RSA PRIVATE KEY или PRIVATE KEY

Оба формата хранят одни и те же числа RSA. PKCS#8 добавляет идентификатор алгоритма и необязательное шифрование, поэтому все современные библиотеки предпочитают его, а некоторые не принимают ничего другого.
Что сравниваетсяФормат ключей PKCS #1PKCS #8

Выбор между PKCS#1 и PKCS#8 стоит за многими ошибками «unsupported key format»: обычно с ними сталкиваются, когда ключ, сгенерированный много лет назад командой openssl genrsa, попадает в современную библиотеку. Файл PKCS#1 (-----BEGIN RSA PRIVATE KEY-----) содержит только структуру ключа RSA; файл PKCS#8 (-----BEGIN PRIVATE KEY-----) оборачивает закрытый ключ любого алгоритма вместе с идентификатором алгоритма, а его зашифрованная форма (-----BEGIN ENCRYPTED PRIVATE KEY-----) добавляет стандартную защиту паролем.

Коротко о главном

PKCS#1 RSAPrivateKeyPKCS#8 PrivateKeyInfo / OneAsymmetricKey
СпецификацияRFC 8017, приложение A.1.2RFC 5208, RFC 5958
Метка PEMRSA PRIVATE KEYPRIVATE 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Стандартная схема PBES2 (PBKDF2 плюс AES) по RFC 8018
Вывод OpenSSL 3Только с -traditionalПо умолчанию для genpkey, pkey, rsa
Java PKCS8EncodedKeySpecНе принимаетсяПринимается
Gox509.ParsePKCS1PrivateKeyx509.ParsePKCS8PrivateKey
Node.js createPrivateKeyПринимается (формат определяется по метке PEM)Принимается
WebCrypto importKeyНе принимаетсяФормат pkcs8

Открытым ключам соответствуют PKCS#1 RSAPublicKey (BEGIN RSA PUBLIC KEY) и SubjectPublicKeyInfo (BEGIN PUBLIC KEY); всё остальное ПО рассчитано именно на второй формат.

Что внутри

Закрытый ключ RSA в формате PKCS#1 — это структура DER SEQUENCE, состоящая из версии, модуля n, открытой экспоненты e, закрытой экспоненты d, простых чисел p и q и CRT-параметров dP, dQ и qInv. Поскольку сама структура ничего не говорит об алгоритме, это должна делать метка 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 использует PBES2 с PBKDF2-HMAC-SHA256 и AES-256-CBC.

Конвертация

Скрипт ниже, запущенный с OpenSSL 3.6.5, показывает форматы по умолчанию и конвертацию туда и обратно без потерь.

OpenSSL 3.6: PKCS#8, PKCS#1 и зашифрованный PKCS#8
#!/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

Те же команды конвертируют в PKCS#8 ключи EC в формате SEC 1 (BEGIN EC PRIVATE KEY). Закрытые ключи OpenSSH (BEGIN OPENSSH PRIVATE KEY) — третий формат; команда ssh-keygen -p -m PKCS8 -f key перезаписывает такой ключ на месте в формате PKCS#8.

Какие ошибки встретятся

Самые строгие требования к формату ключа у Java: PKCS8EncodedKeySpec принимает только PKCS#8, а текст ошибки менялся от выпуска к выпуску. Программа ниже загружает по одному PEM-файлу каждого вида, а команды после неё запускают её на Java 17, 21 и 25 в официальных образах Eclipse Temurin.

LoadKey.java: загрузка закрытых ключей PEM через 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);
      }
    }
  }
}
Запуск на Java 17, 21 и 25 в каталоге с ключами PKCS#8 и 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
Результат (20 октября 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

Старые ответы в интернете цитируют текст Java 17; в Java 21 и 25 сообщение верхнего уровня — Unable to decode key, а «algid parse error, not a sequence» остаётся только в причине исключения (cause). Ищите в логах оба варианта.

Другие среды выполнения (воспроизведено в тот же день с ключами, сгенерированными 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): PKCS#1 в DER, объявленный как type: "pkcs8", отклоняется с ошибкой error:068000A8:asn1 encoding routines::wrong tag (код ERR_OSSL_ASN1_WRONG_TAG); тело PKCS#1 под меткой BEGIN PRIVATE KEY — с ошибкой error:1E08010C:DECODER routines::unsupported; те же байты PKCS#1, переданные в createPublicKey как spki, дают Failed to read asymmetric key. Зашифрованный ключ, загруженный без парольной фразы, отклоняется с ошибкой error:07880109:common libcrypto routines::interrupted or cancelled.
  • Ключи, зашифрованные устаревшими алгоритмами: традиционный PEM-ключ, зашифрованный DES, или ключ PKCS#8 под PBES1 с MD5 и DES загружается в OpenSSL 3 только с провайдером legacy. Без него openssl pkey выводит error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported и следом алгоритм, который не удалось получить (DES-CBC или PBKDF1 для PBES1), а Node.js сообщает error:0308010C:digital envelope routines::unsupported (ERR_OSSL_EVP_UNSUPPORTED).
  • Python (cryptography 46): load_pem_private_key одинаково читает PKCS#1 и PKCS#8, а для зашифрованного ключа без пароля выбрасывает TypeError: Password was not given but private key is encrypted.
  • WebCrypto: importKey("pkcs8", …) с байтами PKCS#1 завершается ошибкой DataError; в Node.js 26 текст ошибки — Invalid keyData, у браузеров — свой.

В каталоге ошибок есть страница для каждой из них; ссылки ниже.

То же разделение у открытых ключей

У открытых ключей те же две формы. PKCS#1 RSAPublicKey (BEGIN RSA PUBLIC KEY) содержит только модуль и экспоненту; SubjectPublicKeyInfo (BEGIN PUBLIC KEY, RFC 5280) оборачивает ключ вместе с идентификатором алгоритма, подходит для любого алгоритма, и именно его ожидают сертификаты, конвертеры JWK, X509EncodedKeySpec в Java, ParsePKIXPublicKey в Go и импорт spki в WebCrypto. Для конвертации между ними служат команды openssl rsa -pubin -in key.pem -RSAPublicKey_out и openssl pkey -pubin -in key.pem -pubout. Путаница возникает в обе стороны: инструменты, которые записывают SPKI, часто называют файл в стиле id_rsa.pub, а собственные однострочные открытые ключи OpenSSH (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): PKCS#1 через -traditional, в идеале только в момент передачи ключа такой программе.
  • Ключ вместе с сертификатом для Windows, macOS или хранилищ ключей Java: PKCS#12, внутри которого ключи хранятся в PKCS#8.
  • Ключи SSH: формат OpenSSH для самого ssh; PKCS#8, если тот же ключ нужен другому инструменту.

Заметки о миграции

  1. Найдите файлы PKCS#1 и SEC 1: grep -l "BEGIN RSA PRIVATE KEY\|BEGIN EC PRIVATE KEY" по репозиториям конфигурации и хранилищам секретов.
  2. Конвертируйте один раз командой openssl pkcs8 -topk8 -nocrypt (или -v2 aes-256-cbc, чтобы зашифровать) и обновите все программы, которые используют ключ; ключевой материал и соответствующий сертификат не меняются.
  3. Перешифруйте старые зашифрованные ключи: традиционное шифрование PEM (заголовки Proc-Type и DEK-Info) получает ключ за одну итерацию MD5, а для ключей, зашифрованных DES или RC2 в любом из форматов, нужен провайдер legacy в OpenSSL; перешифруйте их с PBES2 и AES-256.
  4. Переведите код на универсальные парсеры (ParsePKCS8PrivateKey, load_pem_private_key, createPrivateKey), чтобы новые алгоритмы, включая Ed25519 и ML-DSA, работали без изменения кода.
  5. Проверьте результат: подпишите данные преобразованным ключом и проверьте подпись по существующему сертификату; генератор ключей и просмотр структуры ASN.1 показывают структуру, ничего не отправляя на сервер.

Источники

Все сравнения