PKCS#1 vs PKCS#8 é a questão por trás de muitos erros de “unsupported key format”, que costumam surgir quando uma chave gerada anos atrás com openssl genrsa encontra uma biblioteca moderna. Um arquivo PKCS#1 (-----BEGIN RSA PRIVATE KEY-----) contém apenas uma estrutura de chave RSA; um arquivo PKCS#8 (-----BEGIN PRIVATE KEY-----) envolve a chave privada de qualquer algoritmo com um identificador de algoritmo, e a forma criptografada dele (-----BEGIN ENCRYPTED PRIVATE KEY-----) acrescenta proteção por senha padronizada.
Visão geral
PKCS#1 RSAPrivateKey | PKCS#8 PrivateKeyInfo / OneAsymmetricKey | |
|---|---|---|
| Especificação | RFC 8017, apêndice A.1.2 | RFC 5208, RFC 5958 |
| Rótulo PEM | RSA PRIVATE KEY | PRIVATE KEY; criptografada: ENCRYPTED PRIVATE KEY |
| Algoritmos | Apenas RSA | Qualquer um: RSA, EC, Ed25519, X25519, ML-DSA, ML-KEM … |
| Identificador de algoritmo | Nenhum (implícito no rótulo) | rsaEncryption, id-ecPublicKey, 1.3.101.112 … |
| Criptografia | Cabeçalhos PEM específicos do OpenSSL (Proc-Type, DEK-Info), KDF fraca | PBES2 padronizado (PBKDF2 mais AES), conforme a RFC 8018 |
| Saída do OpenSSL 3 | Só com -traditional | Padrão em genpkey, pkey, rsa |
PKCS8EncodedKeySpec do Java | Não aceito | Aceito |
| Go | x509.ParsePKCS1PrivateKey | x509.ParsePKCS8PrivateKey |
createPrivateKey do Node.js | Aceito (rótulo PEM detectado) | Aceito |
importKey da WebCrypto | Não aceito | Formato pkcs8 |
Os equivalentes para chaves públicas são o RSAPublicKey do PKCS#1 (BEGIN RSA PUBLIC KEY) e o SubjectPublicKeyInfo (BEGIN PUBLIC KEY); este último é o que todo o resto espera.
O que há dentro
Uma chave privada RSA PKCS#1 é uma SEQUENCE DER com versão, módulo n, expoente público e, expoente privado d, os primos p e q e os valores CRT dP, dQ e qInv. Como a estrutura não diz nada sobre o algoritmo, quem precisa dizer é o rótulo PEM; não existe forma PKCS#1 para chaves de curva elíptica (o formato análogo do OpenSSL, BEGIN EC PRIVATE KEY, vem do SEC 1, RFC 5915).
O PKCS#8 envolve uma chave específica de um algoritmo em um PrivateKeyInfo: uma versão, um AlgorithmIdentifier e uma OCTET STRING com a chave interna, que, no caso do RSA, é exatamente a estrutura PKCS#1. A RFC 5958 (versão 2, OneAsymmetricKey) também pode carregar a chave pública. O EncryptedPrivateKeyInfo acrescenta um algoritmo de criptografia e o texto cifrado; o OpenSSL 3.6 usa PBES2 com PBKDF2-HMAC-SHA256 e AES-256-CBC quando você pede criptografia.
Conversão
O script abaixo, executado com o OpenSSL 3.6.5, mostra os padrões e uma conversão de ida e volta sem perdas.
#!/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
Os mesmos comandos convertem chaves EC SEC 1 (BEGIN EC PRIVATE KEY) para PKCS#8. As chaves privadas do OpenSSH (BEGIN OPENSSH PRIVATE KEY) são um terceiro formato; ssh-keygen -p -m PKCS8 -f key regrava uma delas no próprio arquivo como PKCS#8.
Erros que você vai encontrar
O Java é o leitor mais rígido: PKCS8EncodedKeySpec aceita apenas PKCS#8, e o texto do erro mudou entre as versões. O programa abaixo carrega um arquivo PEM de cada tipo, e os comandos seguintes o executam no Java 17, 21 e 25 nas imagens oficiais do Eclipse Temurin.
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
Respostas mais antigas na web citam o texto do Java 17; no Java 21 e no 25, a mensagem de nível superior é Unable to decode key, e “algid parse error, not a sequence” sobrevive apenas na causa. Procure as duas nos logs.
Os outros runtimes, reproduzidos no mesmo dia com chaves geradas pelo OpenSSL 3.6.5:
- Go:
x509.ParsePKCS8PrivateKeyretornax509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)(Go 1.25), o que diz exatamente o que aconteceu. - Node.js 26 (
createPrivateKey, OpenSSL 3.6.5): um DER PKCS#1 declarado comotype: "pkcs8"falha comerror:068000A8:asn1 encoding routines::wrong tag(códigoERR_OSSL_ASN1_WRONG_TAG); um corpo PKCS#1 sob um rótuloBEGIN PRIVATE KEYfalha comerror:1E08010C:DECODER routines::unsupported; os mesmos bytes PKCS#1 passados acreatePublicKeycomospkiresultam emFailed to read asymmetric key. Uma chave criptografada carregada sem frase secreta falha comerror:07880109:common libcrypto routines::interrupted or cancelled. - Chaves criptografadas com algoritmos legados: uma chave PEM tradicional criptografada com DES, ou uma chave PKCS#8 sob PBES1 com MD5 e DES, só carrega no OpenSSL 3 com o provedor legacy. Sem ele,
openssl pkeyimprimeerror:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupportedseguido do algoritmo que não conseguiu obter (DES-CBC, ouPBKDF1no caso do PBES1), e o Node.js informaerror:0308010C:digital envelope routines::unsupported(ERR_OSSL_EVP_UNSUPPORTED). - Python (
cryptography46):load_pem_private_keylê PKCS#1 e PKCS#8 da mesma forma e lançaTypeError: Password was not given but private key is encryptedpara uma chave criptografada sem senha. - WebCrypto:
importKey("pkcs8", …)com bytes PKCS#1 é rejeitado com umDataError; o Node.js 26 formula a mensagem comoInvalid keyData, e os navegadores usam o próprio texto.
O catálogo de erros tem uma página para cada um, com links abaixo.
A mesma divisão nas chaves públicas
As chaves públicas têm as mesmas duas formas. O RSAPublicKey do PKCS#1 (BEGIN RSA PUBLIC KEY) contém apenas o módulo e o expoente; o SubjectPublicKeyInfo (BEGIN PUBLIC KEY, RFC 5280) envolve a chave com um identificador de algoritmo, funciona com qualquer algoritmo e é o que esperam os certificados, os conversores de JWK, o X509EncodedKeySpec do Java, o ParsePKIXPublicKey do Go e a importação spki da WebCrypto. openssl rsa -pubin -in key.pem -RSAPublicKey_out e openssl pkey -pubin -in key.pem -pubout convertem entre os dois. A confusão vai nos dois sentidos: ferramentas que gravam SPKI muitas vezes dão ao arquivo um nome no estilo id_rsa.pub, e as chaves públicas de uma linha do próprio OpenSSH (ssh-rsa AAAA…) são mais um formato, que pode ser convertido com ssh-keygen -e -m PKCS8.
Quando escolher cada um
- Chaves novas, qualquer algoritmo: PKCS#8 (criptografado em disco, sem criptografia dentro de um gerenciador de segredos que já criptografa).
- Java, Go, .NET, WebCrypto, importações em KMS de nuvem: PKCS#8 (DER ou PEM).
- Software antigo que só lê
RSA PRIVATE KEY(alguns appliances, código Ruby e PHP mais antigo, ferramentas SSH legadas): PKCS#1 via-traditional, de preferência só no ponto de entrega. - Chave mais certificado para o Windows, o macOS ou keystores Java: PKCS#12, que contém chaves PKCS#8 internamente.
- Chaves SSH: o formato do OpenSSH para o próprio
ssh; PKCS#8 quando outra ferramenta precisa da mesma chave.
Notas de migração
- Encontre os arquivos PKCS#1 e SEC 1:
grep -l "BEGIN RSA PRIVATE KEY\|BEGIN EC PRIVATE KEY"nos repositórios de configuração e nos cofres de segredos. - Converta uma vez com
openssl pkcs8 -topk8 -nocrypt(ou-v2 aes-256-cbcpara criptografar) e atualize os consumidores; o material da chave e o certificado correspondente não mudam. - Troque a criptografia das chaves antigas: a criptografia PEM tradicional (cabeçalhos
Proc-TypeeDEK-Info) deriva a chave com uma única iteração de MD5, e chaves criptografadas com DES ou RC2, em qualquer um dos dois formatos, precisam do provedor legacy do OpenSSL; criptografe-as de novo com PBES2 e AES-256. - Atualize o código para os parsers genéricos (
ParsePKCS8PrivateKey,load_pem_private_key,createPrivateKey), para que novos algoritmos, incluindo Ed25519 e ML-DSA, funcionem sem mudanças no código. - Verifique assinando com a chave convertida e conferindo a assinatura com o certificado existente; o gerador de chaves e o explorador de ASN.1 mostram a estrutura sem enviar nada.