Skip to content
Voltar ao blog
Segurança

Erro de formato de chave privada RS256: uma mensagem, sete causas

O Node lança o mesmo erro DECODER para contêiner errado, cabeçalho indentado ou chave pública. Correções testadas PKCS#1 e PKCS#8. Gerador online gratuito.

13 min de leitura

Erro de formato de chave privada RS256: uma mensagem, sete causas

Um erro de formato de chave privada RS256 quase nunca revela a própria causa. No Node v25.8.2, cada um destes enganos produz exatamente a mesma linha:

code:    ERR_OSSL_UNSUPPORTED
message: error:1E08010C:DECODER routines::unsupported

Cinco coisas sem relação nenhuma entre si disparam esse erro: um contêiner OpenSSH onde se esperava uma chave PEM, uma linha -----BEGIN indentada, uma chave pública entregue a quem ia assinar, sequências \n literais que ninguém desescapou e um arquivo cujas quebras de linha se perderam no caminho. A lista testada da Seção 2 chega a sete. Sempre a mesma linha, e é por isso que buscar pelo texto do erro joga você na thread de outra pessoa sobre a causa de outra pessoa.

Antes de tudo, corte o problema ao meio:

Triagem de trinta segundos para o primeiro caso:

openssl rsa -in key.pem -noout -text | head -1

Se isso der erro, o problema é o arquivo, e as Seções 3 a 6 vão encontrá-lo. Se funcionar, o OpenSSL entendeu o contêiner, e o seu problema é a biblioteca ou o que você entregou a ela; veja as Seções 4 e 7.

Tudo o que vem abaixo foi medido em 2026-08-11 com OpenSSL 3.6.2 7 Apr 2026, Node v25.8.2, Go go1.26.1 darwin/arm64 e Java 1.8.0_162. Quando uma afirmação vem da leitura do código-fonte, e não de execução, o texto diz isso.

1. Primeiro, descubra qual das duas falhas é a sua

A linha divisória é se um objeto de chave chegou a existir. Falhas em tempo de parsing acontecem antes de qualquer criptografia rodar. A biblioteca lê o seu PEM, não consegue transformá-lo em chave e lança a exceção. Nada foi assinado, nada foi verificado, e o token que você está depurando nunca chegou a ser produzido. Falhas em tempo de verificação são o oposto: a chave carregou sem problemas, uma assinatura foi calculada e ela não bateu. Essas vêm de divergências no nível dos bytes entre quem assina e quem verifica, e o guia de invalid signature cobre esse terreno.

Para distinguir as duas, basta olhar o stack trace. Uma falha de parsing cita um decoder, uma key spec ou uma estrutura ASN.1. Uma falha de verificação cita uma assinatura.

Veja como uma chave privada rejeitada aparece em três ecossistemas:

RuntimeVersão testadaMensagem quando a chave não carrega
Node cryptov25.8.2error:1E08010C:DECODER routines::unsupported
Go crypto/x509go1.26.1 darwin/arm64x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)
Java PKCS8EncodedKeySpec1.8.0_162InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence

Repare como elas diferem em utilidade. O Go diz exatamente qual função chamar no lugar. O Java menciona um “algid” e uma “sequence” e deixa você deduzir sozinho que a sua chave está no contêiner errado. O Node não diz absolutamente nada aproveitável.

Se você não tem certeza de que o token que está perseguindo é mesmo RS256, cole-o no decodificador JWT e leia o alg do cabeçalho antes de seguir. Um cabeçalho HS256 significa que você precisa de um segredo compartilhado, não de um par de chaves, e todos os sintomas deste artigo vão apontar na direção errada.

2. Do texto do erro à causa raiz: a tabela de consulta do erro de formato de chave privada RS256

Ache a sua string exata. A coluna da direita diz para onde ir em seguida.

Texto do erroDe onde vemO que realmente significa
error:1E08010C:DECODER routines::unsupportedNode v25.8.2Sete causas possíveis, listadas abaixo
error:07880109:common libcrypto routines::interrupted or cancelledNode v25.8.2A chave está criptografada e você não passou senha
x509: failed to parse private key (use ParsePKCS8PrivateKey instead for this key format)Go 1.26.1Você chamou ParsePKCS1PrivateKey em um arquivo PKCS#8
x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)Go 1.26.1Você chamou ParsePKCS8PrivateKey em um arquivo PKCS#1
asn1: structure error: tags don't match (16 vs {class:0 tag:6 ...})Go 1.26.1O primeiro bloco PEM é EC PARAMETERS, não a chave
algid parse error, not a sequenceJava 1.8.0_162PKCS#1 entregue a PKCS8EncodedKeySpec
secretOrPrivateKey must have a valuejsonwebtoken, no código-fonteO argumento da chave é falsy e alg não é none
secretOrPrivateKey is not valid key materialjsonwebtoken, no código-fonteNão foi possível construir nem chave privada nem chave secreta
secretOrPrivateKey must be a symmetric key when using ${header.alg}jsonwebtoken, no código-fontealg começa com HS, mas a chave não é um segredo
secretOrPrivateKey must be an asymmetric key when using ${header.alg}jsonwebtoken, no código-fontealg casa com RS, PS ou ES, mas a chave não é privada
secretOrPrivateKey has a minimum key size of 2048 bits for ${header.alg}jsonwebtoken, no código-fonteRS ou PS com chave abaixo de 2048 bits e allowInsecureKeySizes desligado

As cinco strings secretOrPrivateKey foram lidas do sign.js na branch master do jsonwebtoken, e não executadas localmente; trate as condições de disparo como o que o código-fonte diz, não como algo reproduzido nesta máquina. O trecho ${header.alg} é um placeholder de template nesse código; em tempo de execução você verá ali o nome do seu próprio algoritmo, e é por isso que buscar pela string literal com as chaves não encontra nada.

As sete maneiras de produzir DECODER routines::unsupported

Todas as sete foram reproduzidas com crypto.createPrivateKey() no Node v25.8.2, e todas as sete devolveram o mesmo código e a mesma mensagem:

  1. Um contêiner OpenSSH. O arquivo começa com -----BEGIN OPENSSH PRIVATE KEY----- e não é uma estrutura de chave PEM de jeito nenhum.
  2. Uma linha -----BEGIN indentada, ou uma linha -----END indentada. As linhas do corpo estão isentas; a Seção 5 traz o limite exato.
  3. Espaço em branco antes do PEM inteiro. Uma linha em branco no início não é problema; um espaço, sim.
  4. Quebras de linha removidas por completo, de modo que cabeçalho, base64 e rodapé ficam grudados numa linha só.
  5. Uma chave pública onde se esperava uma chave privada.
  6. Sequências barra invertida + n deixadas literais, que é no que uma variável de ambiente de uma linha só transforma a chave.
  7. Um delimitador com o número errado de traços, ou begin/end escritos em minúsculas.

Dois desses são problemas de contêiner, quatro são problemas de texto adulterado e um é pura troca de arquivo. A mensagem não consegue dizer qual, então o caminho mais rápido é eliminar, não ler.

O que o Node aceita, e por que isso encurta a busca

A lista inversa é mais útil, porque cada item dela é uma hipótese que você pode descartar na hora. No Node v25.8.2, crypto.createPrivateKey() aceitou tudo o que está abaixo sem reclamar:

  • Chaves privadas PKCS#1 e PKCS#8
  • Chaves privadas EC SEC1
  • Quebras de linha CRLF
  • Ausência da quebra de linha final
  • Corpo base64 em uma única linha sem dobra
  • Linhas do corpo indentadas
  • Uma linha em branco antes do PEM
  • Um BOM UTF-8, tanto como '' + pem quanto como um Buffer começando por 0xEF 0xBB 0xBF
  • Um cabeçalho PKCS#1 envolvendo um corpo PKCS#8

Esse último caso merece atenção. O decoder lê a estrutura DER dentro do base64 e ignora o rótulo de fora, então um arquivo que diz BEGIN RSA PRIVATE KEY sobre conteúdo PKCS#8 carrega assim mesmo. Bom saber, e também um aviso sobre a Seção 3: a linha de cabeçalho é uma pista, não uma garantia.

3. A linha de cabeçalho PEM: qual contêiner você tem de fato

Todo PEM se anuncia na primeira linha. Estes são os valores de cabeçalho escritos pelo OpenSSL 3.6.2:

ConteúdoPrimeira linha
Chave privada PKCS#8-----BEGIN PRIVATE KEY-----
Chave privada PKCS#1-----BEGIN RSA PRIVATE KEY-----
Chave privada criptografada-----BEGIN ENCRYPTED PRIVATE KEY-----
Chave privada OpenSSH-----BEGIN OPENSSH PRIVATE KEY-----
Chave privada EC SEC1-----BEGIN EC PARAMETERS-----, e depois um segundo bloco -----BEGIN EC PRIVATE KEY-----
Chave pública SPKI-----BEGIN PUBLIC KEY-----
Chave pública PKCS#1-----BEGIN RSA PUBLIC KEY-----
Chave privada Ed25519-----BEGIN PRIVATE KEY-----, e o arquivo inteiro tem três linhas

Ou seja, head -1 key.pem responde à primeira pergunta de qualquer investigação.

ENCRYPTED PRIVATE KEY não é erro de formato. É uma senha que você esqueceu de passar. O Node reporta esse caso de forma diferente de todos os outros, com ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED e error:07880109:common libcrypto routines::interrupted or cancelled, porque a biblioteca pediu uma senha e não recebeu nada de volta. Não misture essa mensagem com a do DECODER; uma não tem nada a ver com a outra.

OPENSSH PRIVATE KEY é outro mundo. O OpenSSH escreve o próprio contêiner, que não é PKCS#1 nem PKCS#8, apesar de ficar entre delimitadores com cara de PEM. O Node o rejeita de saída, e o mesmo fazem os parsers de crypto/x509 do Go e o PKCS8EncodedKeySpec do JDK. Se a sua chave de assinatura de JWT saiu do ssh-keygen, o bug é esse.

Arquivos EC SEC1 têm dois blocos. O openssl ecparam -genkey escreve primeiro um bloco EC PARAMETERS e a chave privada depois. Qualquer coisa que leia apenas o primeiro bloco PEM pega os parâmetros e falha de um jeito que não menciona nem uma coisa nem outra. A Seção 4 traz a versão em Go dessa falha.

E, como o cabeçalho é só um rótulo, a checagem inversa também importa: um arquivo cujo cabeçalho diz uma coisa e cujo DER diz outra é interpretado pelo DER. Ler o head -1 é confiável para arquivos que saíram direto do OpenSSL, e nada confiável para arquivos que passaram por uma pessoa, por uma página de wiki ou por um script fazendo substituição de string.

4. Qual biblioteca aceita o quê: PKCS#1 vs PKCS#8 em três ecossistemas

Esta é a matriz que explica a maioria das discussões de formato entre times. Cada linha foi medida nas versões listadas no início deste artigo.

BibliotecaPKCS#1PKCS#8OpenSSHO erro se explica sozinho?
Node cryptoSimSimNãoNão. Muitas causas, um só DECODER routines::unsupported
Go crypto/x509Sim, função dedicadaSim, função dedicadaNãoSim. Ele diz para qual função trocar
Biblioteca padrão do JavaNãoSimNãoNão. algid parse error, not a sequence chega a atrapalhar

Leia as colunas, e as discussões se resolvem sozinhas. Um serviço em Node e um serviço em Java compartilhando o mesmo arquivo de chave funcionam muito bem até a chave ser PKCS#1. A partir daí o Node continua assinando e o Java lança uma mensagem sobre sequências ASN.1. Ninguém desconfia da chave, porque ela comprovadamente funciona em produção no outro serviço.

Node. Não há nada para configurar. Se o contêiner é PKCS#1 ou PKCS#8, o createPrivateKey() aceita. Quando ele lança exceção, gaste seu tempo nas sete causas da Seção 2, não no formato.

const fs = require('node:fs');
const { createPrivateKey } = require('node:crypto');

try {
  const key = createPrivateKey(fs.readFileSync('key.pem'));
  console.log('parsed:', key.asymmetricKeyType);
} catch (err) {
  console.log(err.code, '/', err.message);
}

Rode isso no arquivo que a sua aplicação carrega, não numa cópia que você fez na mão, e o ramo do catch imprime o par de código e mensagem que você procura na Seção 2.

Go. Dois contêineres, duas funções, e chamar a errada é a falha mais comum em Go. A mensagem diz qual usar, então a correção é mecânica. Tentar as duas em sequência elimina a decisão:

priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
	rsaKey, err2 := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err2 != nil {
		log.Fatalf("neither container parsed: %v / %v", err, err2)
	}
	priv = rsaKey
}

Só que a armadilha do EC precisa ser tratada antes disso. Com um arquivo vindo de openssl ecparam -genkey, o pem.Decode devolve um bloco cujo Type é EC PARAMETERS, e as três funções de parsing falham nele com:

asn1: structure error: tags don't match (16 vs {class:0 tag:6 ...})

Essa mensagem nunca menciona blocos PEM, então a reação natural é desconfiar da chave. Pule o bloco de parâmetros:

block, rest := pem.Decode(pemBytes)
if block == nil {
	log.Fatal("no PEM block found")
}
if block.Type == "EC PARAMETERS" {
	block, _ = pem.Decode(rest)
}

Ou nem gere o bloco extra: acrescente -noout ao comando ecparam que escreve o arquivo.

Java. A biblioteca padrão lê PKCS#8 e mais nada. Entregue uma chave PKCS#1 ao PKCS8EncodedKeySpec no Java 1.8.0_162 e você recebe:

InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence

“algid” é o identificador do algoritmo, o campo que o PKCS#8 acrescenta e que o PKCS#1 não tem. O parser procurou por ele, encontrou o começo de um módulo RSA e desistiu. A mensagem é correta e inútil em doses iguais. Converta o arquivo e o erro some:

openssl pkcs8 -topk8 -nocrypt -in pkcs1.pem -out pkcs8.pem

Depois que o arquivo vira PKCS#8, o caminho de carregamento no Java 8 é curto o bastante para caber num teste enquanto você confirma a correção:

String pem = new String(Files.readAllBytes(Paths.get("key.pem")), StandardCharsets.UTF_8)
        .replace("-----BEGIN PRIVATE KEY-----", "")
        .replace("-----END PRIVATE KEY-----", "")
        .replaceAll("\\s+", "");
byte[] der = Base64.getDecoder().decode(pem);
PrivateKey key = KeyFactory.getInstance("RSA")
        .generatePrivate(new PKCS8EncodedKeySpec(der));

A alternativa a converter é adicionar o BouncyCastle, que lê PKCS#1. Converter é um comando e nenhuma dependência, então converta, a menos que outra coisa na sua stack já precise da biblioteca.

5. Os caracteres que você não consegue ver

É aqui que o conselho mais repetido está errado, e dá para demonstrar isso.

Indentação: o contrário do que te ensinaram

Uma instrução que circula muito diz que toda linha de um PEM, exceto os delimitadores, precisa começar na coluna zero. Testado no Node v25.8.2, é o oposto:

Alteração no arquivoResultado
Todas as linhas indentadasFalha
Só a linha -----BEGIN indentadaFalha
Só a linha -----END indentadaFalha
Só as linhas do corpo base64 indentadasAceito
Um espaço antes do PEM inteiroFalha
Uma linha em branco antes do PEM inteiroAceito

A regra, portanto, é: as linhas -----BEGIN e -----END precisam começar na coluna zero, e a indentação das linhas do corpo não faz diferença. As duas linhas que o conselho isenta são justamente as que quebram, e as linhas que ele manda alinhar são as que têm folga.

Isso importa pelo jeito como as chaves privadas acabam indentadas. Ninguém indenta um PEM à mão. Acontece quando a chave é colada num bloco YAML, num arquivo de values do Helm, num heredoc do Terraform ou numa string de aspas triplas do Python dentro do corpo de uma classe. Todos esses indentam o conteúdo inteiro de maneira uniforme, delimitadores incluídos, que é o caso da primeira linha daquela tabela.

Barra invertida e n literais vindos de uma variável de ambiente de uma linha

Um PEM tem quebras de linha; uma variável de ambiente, na prática, não. Então as chaves aterrissam nos arquivos .env como uma linha só, com o \n escrito como dois caracteres. O que quer que leia esse arquivo entrega ao seu código uma string cheia de barras invertidas, e o parser vê um delimitador seguido de lixo. No Node isso é a causa 6 da Seção 2, com a mesma mensagem DECODER routines::unsupported de todo o resto.

Desfaça isso no ponto de uso:

const pem = process.env.PRIVATE_KEY.replace(/\\n/g, '\n');

Coloque duas proteções em volta disso. Primeiro, só aplique a substituição quando a string realmente contiver a sequência de dois caracteres, assim um valor genuinamente multilinha vindo de outro loader fica intacto. Segundo, prefira base64 para o PEM inteiro, se a sua plataforma permitir: guarde uma linha de base64, decodifique na inicialização, e a questão do escape deixa de existir.

BOM: inofensivo no Node, e não testado no resto

Um byte order mark são três bytes, EF BB BF, que alguns editores do Windows escrevem no início de um arquivo UTF-8. O conselho de removê-lo antes de carregar uma chave é comum. No Node v25.8.2 ele não fez diferença nenhuma: um PEM prefixado com o BOM foi lido com sucesso tanto como string quanto como Buffer começando por esses três bytes.

Delimite bem esse resultado. Ele foi medido apenas no Node v25.8.2. Java, Python e outros parsers não foram testados aqui, e nada neste artigo diz como eles se comportam. Se você está depurando um serviço Java, o BOM continua sendo uma questão em aberto, não uma hipótese descartada.

O BOM quebra outras coisas, e provavelmente foi daí que veio o conselho sobre chaves, por associação. JSON.parse sobre uma string prefixada com BOM é uma falha real e bem documentada, coberta no guia do BOM UTF-8 e erros de JSON.parse. Um arquivo de chave guardado dentro de um config JSON pode, portanto, falhar muito antes de alguém sequer olhar para a chave.

Fim de linha, quebra de linha final e largura da dobra

Mais três suspeitos que o Node v25.8.2 inocentou:

  • Quebras de linha CRLF. Aceitas. Uma chave que passou pelo Windows não está automaticamente quebrada.
  • Ausência da quebra de linha final. Aceita. Este caso depende do parser: há relatos de parsers que rejeitam um PEM sem a quebra de linha final, e o Node não é um deles. O Node aceita; outros parsers não foram testados aqui.
  • Corpo sem dobra. Aceito. O base64 não precisa ser dobrado a cada 64 caracteres.

O que de fato quebra um corpo base64 é um caractere perdido, inserido ou trocado, que é uma falha diferente da dobra. Um cliente de chat que transforma uma quebra de linha em espaço, ou um campo de texto que engole o último caractere, produz um corpo que não decodifica mais. Copie com um botão de copiar, não arrastando o mouse.

6. O OpenSSL 3.x trocou o padrão sem te avisar

Medido no OpenSSL 3.6.2 7 Apr 2026:

ComandoContêiner que ele escreve
openssl genrsa -out k.pem 2048PKCS#8, cabeçalho BEGIN PRIVATE KEY
openssl genrsa -traditional -out k.pem 2048PKCS#1
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048PKCS#8
openssl genpkey -algorithm ED25519PKCS#8
openssl pkcs8 -topk8 -nocrypt -in a.pem -out b.pemConverte PKCS#1 em PKCS#8
openssl rsa -in b.pem -traditional -out a.pemConverte PKCS#8 em PKCS#1

Leia as duas primeiras linhas de novo. Nesta build, o genrsa te dá PKCS#8 por padrão, e é o -traditional que produz o arquivo BEGIN RSA PRIVATE KEY. Muitos guias ainda descrevem o genrsa como o comando de PKCS#1 e o genpkey como o de PKCS#8, e segui-los vai deixar você convicto de que gerou um formato que não gerou.

A consequência prática aparece nas migrações. Um time em Java recebe uma chave que funciona, vinda de um colega que roda um OpenSSL mais antigo, tudo em ordem — e seis meses depois alguém regenera a chave numa máquina nova. Mesmo comando, mesma documentação, contêiner diferente, e agora o JDK lança algid parse error, not a sequence contra uma chave que “foi gerada exatamente do mesmo jeito”. Não foi.

Não presuma, então. Confira:

head -1 key.pem

Uma linha de saída, e a tabela da Seção 3 diz o que você tem em mãos. Faça isso antes de rodar qualquer comando de conversão, porque converter um arquivo PKCS#8 para PKCS#8 é uma operação nula que parece uma correção e não corrige nada.

Se você prefere não pensar nas flags, o gerador de chaves RSA emite os dois contêineres a partir do mesmo par de chaves com um botão de alternância: dá para produzir uma cópia PKCS#1 e uma PKCS#8 da mesma chave e testar cada uma contra a biblioteca que está te recusando.

7. O piso de 2048 bits que rejeita uma chave perfeitamente válida

Uma falha parece problema de formato e não é. No código-fonte do jsonwebtoken, o sign.js lança:

secretOrPrivateKey has a minimum key size of 2048 bits for ${header.alg}

O código-fonte dispara isso quando alg é um algoritmo RS ou PS, a chave tem menos de 2048 bits e allowInsecureKeySizes não foi definido. Essa verificação é da própria biblioteca, não do runtime. O Node v25.8.2 lê uma chave RSA de 1024 bits sem reclamar; modulusLength: 1024 produz um objeto de chave como qualquer outro. Ou seja: a chave é estruturalmente válida, o contêiner está certo, o OpenSSL a lê, e a chamada de assinatura falha assim mesmo.

O sinal é que essa mensagem cita um número. Erros de formato falam de decoders, sequências e material de chave; este fala de bits. Se você vê um tamanho na mensagem, pare de olhar para o PEM.

De onde vêm as chaves de 1024 bits costuma ser história antiga: uma chave gerada anos atrás segundo um padrão que mudou desde então, ou uma fixture de teste que ninguém revisitou porque chaves pequenas são mais rápidas de gerar. A correção é gerar um novo par com 2048 bits ou mais, e não recorrer à válvula de escape, porque ela desliga uma verificação que existe por um motivo.

Para confirmar que o tamanho é o único problema restante, assine o mesmo payload com uma chave nova do tamanho certo no codificador JWT. Se isso produz um token e o seu próprio código não, a diferença está na sua chave, não nas suas claims nem na sua configuração.

8. Um fluxo repetível para falhas de chave RS256

Rode estes passos em ordem. Cada um encontra a causa ou elimina um ramo.

  1. Leia a linha de cabeçalho. head -1 key.pem, e então compare com a tabela da Seção 3. Isso te diz o contêiner, se o arquivo está criptografado e se é uma chave OpenSSH que nunca vai funcionar.
  2. Peça ao OpenSSL para fazer o parsing. openssl rsa -in key.pem -noout -text | head -1 para RSA, ou openssl pkey -in key.pem -noout para qualquer algoritmo. Sucesso significa que os bytes são uma chave válida e o problema está do lado da biblioteca. Falha significa que o arquivo está danificado e você segue para o passo 4.
  3. Confira a linha da sua biblioteca na matriz. Seção 4. Se você está em Java com um arquivo PKCS#1, ou em Go chamando a função de parsing errada, seu trabalho acaba aqui.
  4. Olhe os caracteres invisíveis. head -c 32 key.pem | xxd mostra os primeiros bytes, o que pega um BOM, um espaço no início e um delimitador indentado numa olhada só. Depois confirme que as linhas -----BEGIN e -----END começam na coluna zero, conforme a Seção 5.
  5. Faça uma bisseção com uma chave sabidamente boa. Gere um par novo no gerador de chaves RSA, aponte seu código para ele e veja se o erro sobrevive. Se sobreviver, o bug está no seu código de carregamento, e não no arquivo de chave, e nenhuma reformatação do original vai ajudar. Se sumir, a culpa é do arquivo original, e agora você tem uma chave funcional para comparar.
  6. Confira o algoritmo e o tamanho por último. Confirme que o cabeçalho diz RS256 e que a chave tem pelo menos 2048 bits, conforme a Seção 7.

O passo 5 é o que as pessoas pulam e o que economiza mais tempo. Uma chave de referência limpa transforma um vago “a chave não funciona” numa resposta binária sobre qual dos dois lados está quebrado.

FAQ

Qual é a diferença entre BEGIN RSA PRIVATE KEY e BEGIN PRIVATE KEY?

São dois contêineres em volta da mesma chave RSA. BEGIN RSA PRIVATE KEY é PKCS#1 e guarda os números do RSA diretamente; BEGIN PRIVATE KEY é PKCS#8 e acrescenta um identificador de algoritmo, que é o motivo de ele também poder carregar chaves ECDSA e Ed25519. Qual dos dois você precisa depende da biblioteca, e o gerador de chaves RSA escreve qualquer um deles.

Por que o openssl genrsa produz um formato diferente do que o tutorial mostra?

Porque o padrão mudou. No OpenSSL 3.6.2, openssl genrsa -out k.pem 2048 escreve PKCS#8, com cabeçalho BEGIN PRIVATE KEY. Para obter o layout PKCS#1 tradicional que os guias antigos descrevem, acrescente -traditional. Rode head -1 na saída em vez de confiar em qualquer tutorial sobre o que a sua build produz.

Como corrigir algid parse error, not a sequence no Java?

Essa mensagem no Java 1.8.0_162 significa que você entregou uma chave PKCS#1 ao PKCS8EncodedKeySpec. A biblioteca padrão não lê PKCS#1 de forma alguma. Converta uma vez com openssl pkcs8 -topk8 -nocrypt -in pkcs1.pem -out pkcs8.pem, ou adicione o BouncyCastle se outra coisa no projeto já precisar dele.

Toda linha de uma chave privada precisa começar na coluna zero?

Não, e o conselho mais comum está invertido. Testado no Node v25.8.2, indentar só as linhas do corpo base64 faz o parsing funcionar, enquanto indentar só a linha -----BEGIN ou só a linha -----END falha. Uma linha em branco antes do PEM é aceita; um espaço no início não é.

Como guardar uma chave privada em um arquivo .env?

Ou como uma linha única entre aspas com escapes \n que você desfaz no carregamento com .replace(/\\n/g, '\n'), ou como uma linha de base64 que você decodifica na inicialização. A segunda é mais segura porque não existe convenção de escape para um loader de configuração errar.

Posso usar uma chave de 1024 bits com RS256?

O Node v25.8.2 lê uma chave RSA de 1024 bits sem erro, mas o código-fonte do jsonwebtoken se recusa a assinar com ela: secretOrPrivateKey has a minimum key size of 2048 bits, a menos que allowInsecureKeySizes esteja definido. Gere uma chave de 2048 bits. A mensagem cita uma quantidade de bits, e é assim que você a distingue de um problema de formato.

Por que recebo um erro de formato de chave privada RS256 dizendo que precisa de uma chave assimétrica, se eu passei um arquivo de chave privada?

No código-fonte do jsonwebtoken, secretOrPrivateKey must be an asymmetric key when using ${header.alg} dispara quando alg é RS, PS ou ES e a chave não é uma chave privada. Em geral o valor é uma string de segredo no estilo HS256 que sobrou de uma configuração anterior. Uma string aleatória pertence ao HS256 e ao gerador de segredo JWT; RS256 precisa de um par de chaves, não de um segredo.

Conclusão

O que torna essa classe de bug cara não é a dificuldade. É que uma única string de erro cobre sete causas no Node, que a mensagem do Java aponta para ASN.1 quando a resposta real é “contêiner errado”, e que o conselho de formatação mais repetido sobre o assunto está invertido. Você não chega à resposta lendo; chega eliminando: linha de cabeçalho, parsing pelo OpenSSL, matriz de bibliotecas, caracteres invisíveis, chave sabidamente boa.

Dois hábitos evitam a repetição. Anote qual contêiner cada serviço exige, ao lado da chave no seu cofre de segredos, porque a restrição mora na biblioteca e não na chave. E mantenha um par de chaves sabidamente bom no seu ambiente de desenvolvimento puramente como controle, para que a primeira pergunta sobre qualquer falha de chave tenha resposta de sim ou não em um minuto.

Para a questão mais ampla de como essas chaves devem ser emitidas, rotacionadas e escopadas depois que carregam corretamente, veja Boas práticas de segurança JWT: ataques e defesas.

Tags: jwt rsa pem openssl debugging security

Artigos relacionados

Ver todos os artigos