Pular para o conteúdo
ecdsa.com

Guia rápido de interoperabilidade de bibliotecas (Library Interop Cheatsheet)

Você assina na biblioteca A e verifica na biblioteca B — o que quebra? Escolha os dois lados e receba uma lista de verificação das incompatibilidades concretas: codificação da assinatura, tratamento de high-S, formato da chave pública e nonces determinísticos, cada uma com a linha de código que a corrige.

Dados de referência estáticos — sem entradas, sem chamadas de rede, nada a enviar para lugar nenhum

openssl dgst / openssl pkeyutl

navegadores, Deno, webcrypto do Node

Signatário
Verificador
Biblioteca
OpenSSL CLI
WebCrypto (crypto.subtle)
Assinatura
ASN.1 DER
r‖s bruto (P1363)
Low-S
Aceita as duas · gera qualquer uma
Aceita as duas · gera qualquer uma
RFC 6979
Opcional
Não — nonce aleatório
Chave pública
SPKI PEM
JWK
Curvas
P-256, P-384, P-521, secp256k1
P-256, P-384, P-521

Assinatura em OpenSSL CLI → verificação em WebCrypto (crypto.subtle)

2 correções necessárias para funcionar
Falha

Formatos de assinatura incompatíveis: sai ASN.1 DER, espera-se r‖s bruto

OpenSSL CLI produz ASN.1 DER por padrão e WebCrypto (crypto.subtle) espera r‖s bruto. Não há nada de errado com a chave nem com a matemática — o verificador simplesmente não consegue ler os bytes, e um blob DER de comprimento variável tem o comprimento errado para um parser de largura fixa.

Corrija em qualquer um dos lados — as duas codificações carregam exatamente a mesma informação, então a conversão não perde nada e não envolve criptografia.

Lado do signatárioGere r‖s bruto em OpenSSL CLI

Sem opção nativa

A CLI não tem saída P1363 para ECDSA. Extraia os dois INTEGERs da estrutura DER e complete cada um com zeros à esquerda até o tamanho da curva (32 bytes na P-256) — `openssl asn1parse -inform DER -in sig.der` imprime r e s para que você confira a conversão a olho.

Lado do verificadorAceite ASN.1 DER em WebCrypto (crypto.subtle)

Sem opção nativa

Não existe modo DER. Divida a saída de comprimento fixo ao meio, remova os zeros à esquerda de cada metade e envolva os dois inteiros em uma SEQUENCE DER antes de entregá-los a um verificador no estilo do OpenSSL.

† O comportamento varia entre versões ou builds — verifique na sua versão.

Veja também: Conversor DER ⇄ bruto · Formatos de assinatura ECDSA: DER vs. bruto

Confira

Formatos de chave pública incompatíveis: SPKI PEM vs. JWK

OpenSSL CLI exporta a chave pública como SPKI PEM, e WebCrypto (crypto.subtle) espera JWK. São contêineres realmente diferentes: o SPKI envolve o ponto com o identificador da curva, uma JWK expõe as coordenadas x e y como campos base64url, e um ponto SEC1 não passa dos bytes das coordenadas. O material de chave por baixo é idêntico nos três.

OpenSSL CLI: `openssl ec -pubin -in pub.pem -pubout -outform DER` gera a mesma chave como SPKI DER puro; `openssl ec -pubin -in pub.pem -text -noout` imprime em hex o ponto SEC1 na forma não comprimida.

WebCrypto (crypto.subtle): importKey("spki", derBytes, …) recebe SPKI em *DER*: remova o cabeçalho e o rodapé PEM e decodifique o base64 antes, porque o texto PEM em si é rejeitado.

Veja também: Verificador de assinaturas ECDSA

Bom saber

Use P-256 / P-384 / P-521 — as curvas aceitas pelos dois lados

Os conjuntos de curvas não coincidem: secp256k1 só em OpenSSL CLI. Escolha uma chave em uma curva comum aos dois lados e isso nunca acontece.

OpenSSL CLI: Tudo o que o build habilita: as curvas do NIST sobre corpos primos, secp256k1, Brainpool e outras — `openssl ecparam -list_curves` é a referência definitiva para o seu binário.

WebCrypto (crypto.subtle): Só as três curvas do NIST. A secp256k1 não faz parte da especificação, então chaves de Bitcoin e Ethereum exigem, em vez disso, uma biblioteca JavaScript.

Bom saber

OpenSSL CLI usa um nonce aleatório, a menos que você ative a RFC 6979

O caminho de assinatura usual usa um nonce aleatório. A partir da versão 3.2, o parâmetro de assinatura `nonce-type:1` seleciona o ECDSA determinístico (RFC 6979), com o digest informado explicitamente: `openssl dgst -sha256 -sign key.pem -sigopt nonce-type:1` ou `pkeyutl -pkeyopt nonce-type:1 -pkeyopt digest:sha256`.

Esse comportamento é correto, não um bug — mas significa que a mesma chave e a mesma mensagem produzem bytes diferentes a cada execução, então um teste que compara assinaturas byte a byte vai falhar. Compare, em vez disso, os resultados da verificação.

† O comportamento varia entre versões ou builds — verifique na sua versão.

Todas as bibliotecas, lado a lado

Somente os padrões — cada linha pode ser alterada com uma flag ou com outra chamada. Role para o lado para ver todas as colunas.

BibliotecaFormato de assinatura padrãoComo mudarPolítica de low-SRFC 6979Chave pública padrão
OpenSSL CLI
openssl dgst / openssl pkeyutl
signatário
ASN.1 DER
ASN.1 DER — uma SEQUENCE de dois INTEGERs, então uma assinatura P-256 costuma ter de 70 a 72 bytes, e o comprimento muda de uma assinatura para outra.
A CLI não tem modo r‖s bruto — converta a saída DER fora dela.†
Aceita as duas
Nenhuma política de low-S em nenhuma das direções: as assinaturas saem com s na metade superior em cerca de metade das vezes, e a verificação nunca se importa com isso.
Opcional
O caminho de assinatura usual usa um nonce aleatório. A partir da versão 3.2, o parâmetro de assinatura `nonce-type:1` seleciona o ECDSA determinístico (RFC 6979), com o digest informado explicitamente: `openssl dgst -sha256 -sign key.pem -sigopt nonce-type:1` ou `pkeyutl -pkeyopt nonce-type:1 -pkeyopt digest:sha256`.†
SPKI PEM
SPKI PEM: o bloco `-----BEGIN PUBLIC KEY-----` gerado por `openssl ec -pubout`. Note que `-----BEGIN EC PUBLIC KEY-----` não existe — um bloco rotulado como EC é uma chave privada.
Node.js node:crypto
crypto.sign / crypto.verify / KeyObject
ASN.1 DER
ASN.1 DER, a menos que você peça explicitamente a outra codificação.
`dsaEncoding: "ieee-p1363"` no objeto de chave — defina ao assinar *e* ao verificar.
Aceita as duas
Aceita as duas metades e não normaliza nada — por baixo, é o OpenSSL.
Não — nonce aleatório
Nonce aleatório: a mesma chave e a mesma mensagem produzem bytes de assinatura diferentes a cada chamada.
SPKI PEM
`publicKey.export({ type: "spki", format: "pem" })` é a forma comum; a JWK está a uma chamada de distância, com `export({ format: "jwk" })`.
WebCrypto (crypto.subtle)
navegadores, Deno, webcrypto do Node
verificador
r‖s bruto (P1363)
Apenas r‖s bruto, de comprimento fixo: 64 bytes na P-256, 96 na P-384, 132 na P-521. A especificação não define nenhuma outra codificação.
Não há o que alternar — a WebCrypto nunca usa DER. Converta fora da API.
Aceita as duas
Nenhuma regra de low-S ao assinar nem ao verificar.
Não — nonce aleatório
Nonce aleatório, e a API não oferece nenhuma forma de fornecer o seu próprio.
JWK
Você sempre informa um formato ao importar e exportar: “jwk”, “spki” (bytes DER) ou “raw” (o ponto SEC1 na forma não comprimida). JWK é a forma que a maior parte do código web passa adiante.
Go crypto/ecdsa
SignASN1 / VerifyASN1 ou o par de big.Int
ASN.1 DER
`SignASN1` — o caminho que `crypto.Signer` usa — retorna ASN.1 DER. O `Sign`, de nível mais baixo, devolve r e s como `*big.Int`, o que não é nenhum dos dois formatos de transmissão até você codificá-los.
`SignASN1`/`VerifyASN1` para DER; `Sign`/`Verify` com `FillBytes` para o formato bruto de largura fixa.
Aceita as duas
Nenhum tratamento de low-S: a verificação aceita qualquer uma das metades do intervalo.
Não — nonce aleatório
Não é compatível com a RFC 6979. As versões recentes derivam o nonce da chave, da mensagem e de aleatoriedade nova, tudo junto, então os bytes da assinatura mudam a cada execução e não podem ser fixados em testes.†
SPKI DER
`x509.MarshalPKIXPublicKey` produz SPKI DER; um bloco PEM está a um `pem.EncodeToMemory` de distância.
Python cryptography (pyca)
hazmat.primitives.asymmetric.ec
ASN.1 DER
DER — `private_key.sign(...)` retorna a SEQUENCE ASN.1.
`decode_dss_signature` / `encode_dss_signature` convertem entre DER e o par de inteiros; o padding de largura fixa fica por sua conta.
Aceita as duas
Aceita as duas metades; nada normaliza s.
Opcional
Nonce aleatório por padrão. Versões recentes aceitam uma flag de assinatura determinística no objeto ECDSA, e esse caminho exige um OpenSSL recente por baixo — confira com qual versão do OpenSSL seu wheel foi compilado antes de depender disso.†
SPKI PEM
`public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)` — o mesmo bloco PEM que o OpenSSL gera.
Python ecdsa (Python puro)
SigningKey / VerifyingKey, sigencode= e sigdecode=
r‖s bruto (P1363)
r‖s bruto: `sign()` retorna os dois valores concatenados e completados com zeros até o tamanho da curva.
Os argumentos `sigencode=` e `sigdecode=` escolhem a codificação em cada chamada de assinatura e de verificação.
Aceita as duas
As duas metades são aceitas. A biblioteca também traz codificadores que canonicalizam a assinatura, forçando s para a metade inferior — eles existem ao lado dos comuns e precisam ser selecionados explicitamente.†
Opcional
A assinatura determinística da RFC 6979 está disponível como um método de assinatura separado; a chamada sign() comum usa aleatoriedade.
ponto SEC1
As chaves circulam como o ponto puro: `VerifyingKey.to_string()` retorna x‖y sem o prefixo 0x04 por padrão, o que stacks baseadas em SPKI não aceitam como está.†
Java JCA (SunEC)
java.security.Signature
ASN.1 DER
`SHA256withECDSA` produz ASN.1 DER.
Acrescente `inP1363Format` ao nome do algoritmo — mesma classe, formato de transmissão diferente.
Aceita as duas
Nenhuma política de low-S em nenhuma das direções.
Não — nonce aleatório
O SunEC assina com um nonce aleatório. O ECDSA determinístico fica acessível ao adicionar um provedor de terceiros, como o Bouncy Castle.†
SPKI DER
`publicKey.getEncoded()` retorna X.509 SPKI DER. O JDK não tem suporte a PEM — o empacotamento em base64 fica por sua conta.
Rust p256 / k256 (RustCrypto)
ecdsa::Signature, SigningKey, VerifyingKey
r‖s bruto (P1363)
O tipo `Signature` padrão é o par de largura fixa — 64 bytes na P-256 e na secp256k1. DER é uma conversão separada e explícita.†
O par de largura fixa é o tipo padrão; a conversão DER é explícita nas duas direções.†
Aceita as duas
O crate secp256k1 normaliza s para a metade inferior ao assinar, seguindo a convenção do Bitcoin; os crates das curvas do NIST não fazem isso. A verificação aceita as duas metades, então normalize no lado do signatário quando a outra ponta for rigorosa.†
Sim, por padrão
Nonces determinísticos da RFC 6979 por padrão — a mesma chave e a mesma mensagem sempre produzem os mesmos bytes. Uma variante aleatória precisa ser ativada explicitamente.
ponto SEC1
Uma `VerifyingKey` é serializada como um ponto SEC1 codificado — 33 bytes na forma comprimida, 65 na não comprimida. SPKI DER e PEM estão disponíveis pelos traits complementares de SPKI/PKCS#8.†
libsecp256k1 (Bitcoin Core)
secp256k1_ecdsa_sign / _verify
Você escolhe
Nenhum dos dois, até você serializar: a assinatura vive em uma struct interna opaca, e você escolhe o formato compacto (r‖s bruto) ou DER na saída.
Serialize em formato compacto para r‖s bruto e em DER para o formato de transmissão do Bitcoin — a escolha é feita a cada chamada.
Exige low-S
Esta é a rigorosa. Ao assinar, ela sempre gera low-S, e a verificação rejeita de imediato uma assinatura high-S — a biblioteca expõe uma chamada de normalização justamente para você corrigir assinaturas vindas de um signatário de uso geral.
Sim, por padrão
RFC 6979 por padrão; é possível passar outra função de nonce, se você precisar.
ponto SEC1
Pontos SEC1: 33 bytes na forma comprimida ou 65 bytes na não comprimida. Nada de SPKI, PEM ou JWK em nenhum lugar da API.
PHP openssl_*
openssl_sign / openssl_verify
ASN.1 DER
DER — `openssl_sign()` devolve exatamente o que o OpenSSL produziu.
Nenhum modo bruto: converta no seu próprio código ou use uma biblioteca JOSE que já faça isso.
Aceita as duas
Nenhuma política de low-S — herda o comportamento do OpenSSL sem alterações.
Não — nonce aleatório
Nonce aleatório; a extensão não expõe nenhum modo determinístico.
SPKI PEM
Strings PEM na entrada e na saída: `openssl_pkey_get_public()` recebe um bloco `-----BEGIN PUBLIC KEY-----` ou um certificado inteiro.
Ruby OpenSSL
OpenSSL::PKey::EC
ASN.1 DER
DER — `key.sign(digest, data)` retorna a SEQUENCE ASN.1.
Nenhum modo bruto nativo: converta manualmente com OpenSSL::ASN1 ou deixe uma gem JOSE fazer isso.
Aceita as duas
Nenhuma política de low-S — o comportamento do OpenSSL, sem alterações.
Não — nonce aleatório
Nonce aleatório; nenhuma opção determinística é exposta.
SPKI PEM
SPKI PEM. Versões recentes oferecem um método dedicado para exportar a chave pública — vale a pena usá-lo, porque um simples `to_pem` em uma chave que contém a parte privada grava a chave privada.†

† O comportamento varia entre versões ou builds — verifique na sua versão.

Vá além

How it works

A signature that verifies in one library and fails in another is almost never wrong mathematically: the two sides simply disagree about packaging. An ECDSA signature is a pair of integers (r, s), and each ecosystem picked its own way to serialize them — ASN.1 DER in the OpenSSL and X.509 lineage, fixed-width r‖s in WebCrypto, JWS and most blockchain stacks. On top of that sit two more sources of disagreement: whether s must be in the lower half of the range (the low-S rule that Bitcoin enforces and general-purpose libraries ignore), and how the public key itself is wrapped — an SPKI structure, a JWK object, or the bare SEC1 point.

This page compares those defaults for a pair of libraries and lists only the differences that actually cause a failure, together with the switch or conversion that removes each one. Every row describes what a library does when you call the obvious API without extra arguments — not what it is capable of. Nearly all of it is configurable, which is exactly why two teams reading the same documentation end up incompatible, and why the fix is usually one option rather than new code.