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
Assinatura em OpenSSL CLI → verificação em WebCrypto (crypto.subtle)
2 correções necessárias para funcionarFormatos 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.
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.
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
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
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.
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.
| Biblioteca | Formato de assinatura padrão | Como mudar | Política de low-S | RFC 6979 | Chave 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
- Conversor DER ⇄ bruto — cole a assinatura que você já tem e receba a outra codificação, com r, s e a verificação de high-S.
- Verificador de assinaturas ECDSA — encerre a discussão sobre se a assinatura é válida afinal, sem depender de nenhuma das duas bibliotecas.
- Formatos de assinatura ECDSA: DER vs. bruto (P1363) — os fundamentos em nível de byte por trás de cada linha da tabela.
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.