Pular para o conteúdo
ecdsa.com
Node.js

Node.js node:crypto

O módulo crypto embutido no Node.js: assinaturas ECDSA, EdDSA, RSA e pós-quânticas sobre o OpenSSL que acompanha cada versão do Node.js — por padrão, assinaturas em DER, nonces aleatórios e nenhuma política low-S.
Licença
MIT
Versão verificada
24.22.0 ·
Código-fonte
github.com/nodejs/node
Documentação
nodejs.org/api/crypto.html

Visão geral da compatibilidade

ECDSAEdDSAPós-quântica

Curvas ECDSA
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Algoritmos pós-quânticos
ML-KEMML-DSASLH-DSA
Formatos de chave pública
spki-pemspki-derx509jwksec1-uncompressedsec1-compressed
Formatos de chave privada
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derjwkraw-scalar

O node:crypto é o módulo de criptografia que acompanha todas as versões do Node.js. As funções de chamada única crypto.sign() e crypto.verify() e a classe KeyObject cobrem ECDSA, Ed25519, Ed448, RSA e, a partir do Node.js 24.22, ML-DSA, SLH-DSA e ML-KEM. Tudo roda sobre o OpenSSL embutido no Node.js (3.5.8 no Node.js 24.22.0), então a maior parte do comportamento espelha a linha de comando do OpenSSL — e difere da API WebCrypto que o mesmo processo expõe como crypto.subtle.

O bug de interoperabilidade que você tem mais chance de encontrar é a codificação da assinatura. crypto.sign() retorna ASN.1 DER, enquanto WebCrypto, JWS/JWT, .NET e a maioria das carteiras de hardware usam a forma r‖s bruta de tamanho fixo. Uma assinatura na outra codificação não lança exceção: crypto.verify() simplesmente retorna false, o que parece exatamente uma assinatura falsificada. Defina dsaEncoding: "ieee-p1363" no objeto de chave tanto no lado que assina quanto no lado que verifica, ou converta os bytes com o conversor DER ⇄ bruto.

Dois padrões menos visíveis fazem diferença quando o outro lado é rigoroso. As assinaturas nunca são normalizadas para low-S, então cerca de metade delas é rejeitada por verificadores que exigem isso, como a libsecp256k1 e a @noble/curves 2.x. E o nonce é aleatório, então a mesma chave e a mesma mensagem produzem bytes diferentes a cada chamada — nos testes, compare resultados de verificação, nunca os bytes da assinatura. Se um signatário algum dia repetir um nonce, o verificador de reutilização de nonce recupera a chave, e é por isso que um nonce aleatório precisa de uma boa fonte de aleatoriedade.

Se você passar null como algoritmo, uma chave EC assina silenciosamente com SHA-256, qualquer que seja a curva; uma chave P-384 então falha com qualquer contraparte que espere SHA-384. Indique o hash em toda chamada, carregue as chaves uma única vez com crypto.createPublicKey() ou crypto.createPrivateKey() — elas leem PEM, DER, JWK e até o ponto SEC1 isolado — e guarde o KeyObject resultante em vez de analisar strings de novo.

Para conferir uma assinatura produzida em outro lugar, cole-a com a chave no verificador de assinaturas; para ver quais padrões entram em conflito com outra stack, escolha os dois lados na matriz de interoperabilidade de bibliotecas.

Os fatos e as verificações por trás deles

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/node-crypto/run.sh executou Node.js node:crypto 24.22.0 em OpenSSL 3.5.8 na imagem de contêiner node:24.22.0-bookworm-slim em 20 de outubro de 2026; a página precisa corresponder ao que a verificação imprimiu, e cada fato lista essas linhas.

  • Codificação da assinatura ECDSA

    der

    crypto.sign() retorna uma SEQUENCE ASN.1 DER, geralmente de 70 a 72 bytes na P-256. Com dsaEncoding: "ieee-p1363" no objeto de chave, retorna em vez disso a forma r‖s fixa de 64 bytes; a opção precisa ser definida tanto no lado que assina quanto no que verifica.

    Registrado pela verificação
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: default output parses as DER: true; ieee-p1363 output length 64
  • Low-S ao assinar

    either

    As assinaturas não são normalizadas: entre 64 assinaturas feitas no teste, pelo menos uma tinha s na metade superior do intervalo, como se espera de um nonce aleatório.

    Registrado pela verificação
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: high-S seen among 64 signatures
  • High-S ao verificar

    accepts-high-s

    crypto.verify() aceitou uma assinatura cujo s foi substituído por n − s, em DER e na forma bruta. Só uma contraparte que exija low-S estrito vai recusá-la.

    Registrado pela verificação
    fact ecdsa-low-s-verify = accepts-high-s
  • Nonce do ECDSA

    random

    Nonce aleatório: assinar a mesma mensagem duas vezes com a mesma chave gerou bytes diferentes, e a chave de teste da RFC 6979 não reproduziu a assinatura da RFC. O Node.js não oferece opção de ECDSA determinístico.

    Registrado pela verificação
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: same input twice gives identical bytes: false; r equals RFC 6979 A.2.5: false
  • Entrada do hash na chamada de assinatura

    hashes-message

    O hash dos dados é calculado com o algoritmo que você indica — a assinatura foi verificada contra o SHA-256 da mensagem em uma implementação independente. Não há chamada que assine um digest pré-calculado, então passe a própria mensagem.

    Registrado pela verificação
    fact ecdsa-prehash = hashes-message
    evidence ecdsa-prehash: signature verifies against SHA-256(message) in @noble/curves: true
  • Hash padrão

    sha256

    Com null ou undefined como algoritmo, uma chave EC assina com SHA-256, independentemente da curva. Indique o hash explicitamente para chaves P-384 e P-521.

    Registrado pela verificação
    fact ecdsa-default-hash = sha256
  • Assinatura ECDSA malformada

    returns-false

    Uma assinatura r‖s bruta onde se espera DER, DER onde se espera a forma bruta e dados sem sentido fazem, todos, crypto.verify() retornar false; nada lança exceção, então um problema de formato parece uma assinatura inválida.

    Registrado pela verificação
    fact ecdsa-malformed-signature = returns-false
  • Curvas ECDSA

    P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

    P-224, P-256, P-384, P-521, secp256k1 e as curvas Brainpool r1 geraram chaves e fizeram o ciclo completo de assinatura e verificação. crypto.getCurves() lista os nomes do OpenSSL, como prime256v1 para P-256.

    Registrado pela verificação
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 e Ed448 assinam por meio de crypto.sign(null, data, key); o Ed448 também aceita a opção context.

    Registrado pela verificação
    fact eddsa = Ed25519, Ed448
  • Algoritmos pós-quânticos

    ML-KEMML-DSASLH-DSA

    ML-KEM-512/768/1024 (com crypto.encapsulate() e crypto.decapsulate()), ML-DSA-44/65/87 e SLH-DSA funcionaram todos no Node.js 24.22.0, que embute o OpenSSL 3.5.8.

    Registrado pela verificação
    fact pqc = ML-KEM, ML-DSA, SLH-DSA
  • Formatos de chave pública

    spki-pemspki-derx509jwksec1-uncompressedsec1-compressed

    crypto.createPublicKey() leu SPKI em PEM e em DER, um certificado X.509 em PEM, uma JWK e o ponto SEC1 isolado — não comprimido ou comprimido — com format: "raw-public". Um x‖y de 64 bytes sem o prefixo 0x04 foi rejeitado.

    Registrado pela verificação
    fact public-key-formats = spki-pem, spki-der, x509, jwk, sec1-uncompressed, sec1-compressed
  • Formatos de chave privada

    pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derjwkraw-scalar

    crypto.createPrivateKey() leu PKCS#8 em PEM e DER, PKCS#8 criptografado com frase secreta, SEC1 EC PRIVATE KEY em PEM e DER, uma JWK com d e o escalar isolado de 32 bytes com format: "raw-private".

    Registrado pela verificação
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der, jwk, raw-scalar

Uso

Assine e verifique com P-256, indicando o hash e a codificação da assinatura nos dois lados — aqui, r‖s bruto para uma contraparte WebCrypto ou JOSE.
import { generateKeyPairSync, sign, verify } from "node:crypto";

const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "P-256" });
const message = Buffer.from("order #1042 approved");

// Name the hash and the encoding explicitly, on both sides:
// "der" for OpenSSL, Java and Go peers, "ieee-p1363" (raw r||s) for WebCrypto and JOSE.
const signature = sign("sha256", message, { key: privateKey, dsaEncoding: "ieee-p1363" });
const ok = verify("sha256", message, { key: publicKey, dsaEncoding: "ieee-p1363" }, signature);

console.log("signature bytes:", signature.length);
console.log("verified:", ok);

O executor de trechos de código roda cada exemplo literalmente na imagem de contêiner node:24.22.0-bookworm-slim, e a execução falha se a saída não corresponder.

Comando
node usage.mjs
A saída deve corresponder a (expressão regular)
signature bytes: 64\nverified: true

Bibliotecas