Saltar al contenido
ecdsa.com

PKCS#1 o PKCS#8: BEGIN RSA PRIVATE KEY o PRIVATE KEY

Ambos contienen los mismos números RSA. PKCS#8 añade un identificador de algoritmo y un cifrado opcional, y por eso todas las bibliotecas modernas lo prefieren y algunas no aceptan nada más.

PKCS#1 frente a PKCS#8 es la cuestión que hay detrás de muchos errores «unsupported key format», que suelen aparecer cuando una clave generada hace años con openssl genrsa llega a una biblioteca moderna. Un archivo PKCS#1 (-----BEGIN RSA PRIVATE KEY-----) solo contiene una estructura de clave RSA; un archivo PKCS#8 (-----BEGIN PRIVATE KEY-----) envuelve la clave privada de cualquier algoritmo junto con un identificador de algoritmo, y su forma cifrada (-----BEGIN ENCRYPTED PRIVATE KEY-----) añade una protección estándar con contraseña.

De un vistazo

PKCS#1 RSAPrivateKeyPKCS#8 PrivateKeyInfo / OneAsymmetricKey
EspecificaciónRFC 8017, apéndice A.1.2RFC 5208, RFC 5958
Etiqueta PEMRSA PRIVATE KEYPRIVATE KEY; cifrada: ENCRYPTED PRIVATE KEY
AlgoritmosSolo RSACualquiera: RSA, EC, Ed25519, X25519, ML-DSA, ML-KEM …
Identificador de algoritmoNinguno (lo implica la etiqueta)rsaEncryption, id-ecPublicKey, 1.3.101.112 …
CifradoEncabezados PEM propios de OpenSSL (Proc-Type, DEK-Info), KDF débilPBES2 estándar (PBKDF2 más AES) según el RFC 8018
Salida de OpenSSL 3Solo con -traditionalOpción por defecto de genpkey, pkey, rsa
PKCS8EncodedKeySpec de JavaNo se aceptaSe acepta
Gox509.ParsePKCS1PrivateKeyx509.ParsePKCS8PrivateKey
createPrivateKey de Node.jsSe acepta (detecta la etiqueta PEM)Se acepta
importKey de WebCryptoNo se aceptaFormato pkcs8

Sus equivalentes para claves públicas son RSAPublicKey de PKCS#1 (BEGIN RSA PUBLIC KEY) y SubjectPublicKeyInfo (BEGIN PUBLIC KEY); este último es el que espera todo lo demás.

Qué contiene cada uno

Una clave privada RSA PKCS#1 es una SEQUENCE DER con la versión, el módulo n, el exponente público e, el exponente privado d, los primos p y q, y los valores CRT dP, dQ y qInv. Como la estructura no dice nada del algoritmo, tiene que decirlo la etiqueta PEM; no existe una forma PKCS#1 para las claves de curva elíptica (el formato análogo de OpenSSL, BEGIN EC PRIVATE KEY, procede de SEC 1, RFC 5915).

PKCS#8 envuelve una clave propia de un algoritmo en una PrivateKeyInfo: una versión, un AlgorithmIdentifier y un OCTET STRING que contiene la clave interior, que en RSA es exactamente la estructura PKCS#1. El RFC 5958 (versión 2, OneAsymmetricKey) también puede llevar la clave pública. EncryptedPrivateKeyInfo añade un algoritmo de cifrado y el texto cifrado; OpenSSL 3.6 usa PBES2 con PBKDF2-HMAC-SHA256 y AES-256-CBC cuando pides cifrado.

Conversión

El script siguiente, ejecutado con OpenSSL 3.6.5, muestra los valores por defecto y una conversión de ida y vuelta sin pérdidas.

OpenSSL 3.6: PKCS#8, PKCS#1 y PKCS#8 cifrado
#!/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"
Salida
-----BEGIN PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----
-----BEGIN ENCRYPTED PRIVATE KEY-----
-----BEGIN PRIVATE KEY-----
round trip PKCS#1 -> PKCS#8: identical

Los mismos comandos convierten a PKCS#8 las claves EC de SEC 1 (BEGIN EC PRIVATE KEY). Las claves privadas de OpenSSH (BEGIN OPENSSH PRIVATE KEY) son un tercer formato; ssh-keygen -p -m PKCS8 -f key reescribe una de ellas como PKCS#8 en el mismo archivo.

Errores que verás

Java es el lector más estricto: PKCS8EncodedKeySpec solo acepta PKCS#8, y el texto de su error cambió de una versión a otra. El programa siguiente carga un archivo PEM de cada tipo, y los comandos que vienen después lo ejecutan en Java 17, 21 y 25 con las imágenes oficiales de Eclipse Temurin.

LoadKey.java: cargar claves privadas PEM con 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);
      }
    }
  }
}
Ejecutarlo en Java 17, 21 y 25 con una clave PKCS#8 y otra 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
Salida (20 de octubre de 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

Las respuestas más antiguas de la web citan el texto de Java 17; en Java 21 y 25, el mensaje de primer nivel es Unable to decode key, y «algid parse error, not a sequence» solo se conserva en la causa. Busca ambos en los registros.

Los demás entornos de ejecución, reproducidos el mismo día con claves generadas por OpenSSL 3.6.5:

  • Go: x509.ParsePKCS8PrivateKey devuelve x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format) (Go 1.25), que indica exactamente lo que ocurre.
  • Node.js 26 (createPrivateKey, OpenSSL 3.6.5): un DER PKCS#1 declarado como type: "pkcs8" falla con error:068000A8:asn1 encoding routines::wrong tag (código ERR_OSSL_ASN1_WRONG_TAG); un cuerpo PKCS#1 bajo una etiqueta BEGIN PRIVATE KEY falla con error:1E08010C:DECODER routines::unsupported; los mismos bytes PKCS#1 pasados a createPublicKey como spki dan Failed to read asymmetric key. Una clave cifrada que se carga sin frase de contraseña falla con error:07880109:common libcrypto routines::interrupted or cancelled.
  • Claves cifradas con algoritmos heredados: una clave PEM tradicional cifrada con DES, o una clave PKCS#8 protegida con PBES1, MD5 y DES, solo se carga en OpenSSL 3 con el proveedor heredado (legacy). Sin él, openssl pkey muestra error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported seguido del algoritmo que no pudo obtener (DES-CBC, o PBKDF1 en el caso de PBES1), y Node.js informa de error:0308010C:digital envelope routines::unsupported (ERR_OSSL_EVP_UNSUPPORTED).
  • Python (cryptography 46): load_pem_private_key lee PKCS#1 y PKCS#8 por igual y lanza TypeError: Password was not given but private key is encrypted con una clave cifrada sin contraseña.
  • WebCrypto: importKey("pkcs8", …) con bytes PKCS#1 devuelve una promesa rechazada con un DataError; Node.js 26 lo formula como Invalid keyData, y los navegadores usan su propio texto.

El catálogo de errores tiene una página para cada uno, enlazada más abajo.

La misma división en las claves públicas

Las claves públicas tienen las mismas dos formas. RSAPublicKey de PKCS#1 (BEGIN RSA PUBLIC KEY) solo contiene el módulo y el exponente; SubjectPublicKeyInfo (BEGIN PUBLIC KEY, RFC 5280) envuelve la clave junto con un identificador de algoritmo, sirve para cualquier algoritmo y es lo que esperan los certificados, los conversores de JWK, X509EncodedKeySpec de Java, ParsePKIXPublicKey de Go y la importación spki de WebCrypto. openssl rsa -pubin -in key.pem -RSAPublicKey_out y openssl pkey -pubin -in key.pem -pubout convierten de una forma a la otra. La confusión va en ambos sentidos: las herramientas que escriben SPKI suelen nombrar el archivo al estilo de id_rsa.pub, y las claves públicas de una sola línea del propio OpenSSH (ssh-rsa AAAA…) son otro formato más, que se convierte con ssh-keygen -e -m PKCS8.

Cuándo elegir cada uno

  • Claves nuevas, de cualquier algoritmo: PKCS#8 (cifrado en disco; sin cifrar dentro de un gestor de secretos que ya cifra).
  • Java, Go, .NET, WebCrypto, importaciones en KMS en la nube: PKCS#8 (DER o PEM).
  • Software antiguo que solo lee RSA PRIVATE KEY (algunos dispositivos dedicados, código Ruby y PHP antiguo, herramientas SSH heredadas): PKCS#1 mediante -traditional, idealmente solo en el punto de entrega.
  • Clave junto con certificado para Windows, macOS o los almacenes de claves de Java: PKCS#12, que contiene internamente claves PKCS#8.
  • Claves SSH: el formato de OpenSSH para el propio ssh; PKCS#8 cuando otra herramienta necesita la misma clave.

Notas de migración

  1. Localiza los archivos PKCS#1 y SEC 1: grep -l "BEGIN RSA PRIVATE KEY\|BEGIN EC PRIVATE KEY" en los repositorios de configuración y los almacenes de secretos.
  2. Convierte una sola vez con openssl pkcs8 -topk8 -nocrypt (o -v2 aes-256-cbc para cifrar) y actualiza los consumidores; el material de la clave y el certificado correspondiente no cambian.
  3. Vuelve a cifrar las claves cifradas antiguas: el cifrado PEM tradicional (encabezados Proc-Type y DEK-Info) deriva su clave con una sola iteración de MD5, y las claves cifradas con DES o RC2 en cualquiera de los dos formatos necesitan el proveedor heredado de OpenSSL; vuelve a cifrarlas con PBES2 y AES-256.
  4. Pasa el código a los analizadores genéricos (ParsePKCS8PrivateKey, load_pem_private_key, createPrivateKey) para que los algoritmos nuevos, incluidos Ed25519 y ML-DSA, funcionen sin cambiar el código.
  5. Verifica firmando con la clave convertida y comprobando la firma con el certificado existente; el generador de claves y el explorador de ASN.1 muestran la estructura sin enviar nada a ningún servidor.

Referencias

Todas las comparaciones