Перейти к содержимому
ecdsa.com
Node.js

Node.js node:crypto

Встроенный модуль crypto в Node.js: подписи ECDSA, EdDSA, RSA и постквантовые подписи на базе OpenSSL, который входит в каждый релиз Node.js; по умолчанию — подписи в DER, случайные nonce и никакой политики low-S.
Лицензия
MIT
Проверенная версия
24.22.0 ·
Исходный код
github.com/nodejs/node
Документация
nodejs.org/api/crypto.html

Совместимость вкратце

ECDSAEdDSAПостквантовая криптография

Кривые ECDSA
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
Форматы открытых ключей
spki-pemspki-derx509jwksec1-uncompressedsec1-compressed
Форматы закрытых ключей
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derjwkraw-scalar

node:crypto — криптографический модуль, который входит в каждый релиз Node.js. Его одношаговые функции crypto.sign() и crypto.verify() и класс KeyObject поддерживают ECDSA, Ed25519, Ed448, RSA, а начиная с Node.js 24.22 — также ML-DSA, SLH-DSA и ML-KEM. Всё работает поверх OpenSSL, встроенного в Node.js (3.5.8 в Node.js 24.22.0), поэтому поведение в основном повторяет командную строку OpenSSL — и отличается от WebCrypto API, который тот же процесс предоставляет как crypto.subtle.

Самая частая ошибка совместимости — кодировка подписи. crypto.sign() возвращает ASN.1 DER, а WebCrypto, JWS/JWT, .NET и большинство аппаратных кошельков используют формат raw r‖s фиксированной длины. Подпись в другой кодировке не вызывает исключения: crypto.verify() просто возвращает false, и это выглядит точно так же, как поддельная подпись. Задайте dsaEncoding: "ieee-p1363" в объекте ключа и на стороне подписи, и на стороне проверки или преобразуйте байты конвертером DER ⇄ raw.

Ещё две, менее заметные, настройки по умолчанию важны, когда другая сторона проверяет строго. Подписи никогда не нормализуются к low-S, поэтому примерно половину из них отвергают реализации проверки, которые требуют low-S, например libsecp256k1 и @noble/curves 2.x. Кроме того, nonce случаен, поэтому один и тот же ключ с одним и тем же сообщением при каждом вызове дают разные байты: в тестах сравнивайте результаты проверки и никогда не сравнивайте байты подписей. Если подписант хоть раз повторит nonce, проверка повторного использования nonce восстановит ключ, поэтому случайному nonce нужен надёжный источник случайности.

Если передать null в качестве алгоритма, подпись ключом EC молча создаётся с SHA-256 независимо от кривой; тогда подписи ключом P-384 не пройдут проверку на любой стороне, которая ожидает SHA-384. Указывайте хеш в каждом вызове, загружайте ключи один раз через crypto.createPublicKey() или crypto.createPrivateKey() — они читают PEM, DER, JWK и даже голую точку SEC1 — и храните полученный KeyObject, а не разбирайте строки заново.

Чтобы проверить подпись, созданную в другом месте, вставьте её вместе с ключом в инструмент проверки подписей; чтобы увидеть, какие настройки по умолчанию конфликтуют с другим стеком, выберите обе стороны в матрице совместимости библиотек.

Факты и проверки, на которых они основаны

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/node-crypto/run.sh запустила Node.js node:crypto 24.22.0 в среде OpenSSL 3.5.8 в образе контейнера node:24.22.0-bookworm-slim; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Кодировка подписи ECDSA

    der

    crypto.sign() возвращает ASN.1 DER SEQUENCE, обычно от 70 до 72 байт на P-256. С dsaEncoding: "ieee-p1363" в объекте ключа функция вместо этого возвращает форму r‖s фиксированной длины 64 байта; параметр нужно задать и на стороне подписи, и на стороне проверки.

    Что записала проверка
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: default output parses as DER: true; ieee-p1363 output length 64
  • Low-S при подписи

    either

    Подписи не нормализуются: среди 64 подписей, созданных в ходе проверки, хотя бы у одной значение s оказалось в верхней половине диапазона, как и ожидается при случайном nonce.

    Что записала проверка
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: high-S seen among 64 signatures
  • High-S при проверке

    accepts-high-s

    crypto.verify() приняла подпись, в которой s заменили на n − s, и в DER, и в форме raw. Возразить может только сторона, которая строго требует low-S.

    Что записала проверка
    fact ecdsa-low-s-verify = accepts-high-s
  • Nonce в ECDSA

    random

    Случайный nonce: две подписи одного и того же сообщения одним ключом дали разные байты, а с тестовым ключом из RFC 6979 подпись из RFC не воспроизвелась. Детерминированного режима ECDSA в Node.js нет.

    Что записала проверка
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: same input twice gives identical bytes: false; r equals RFC 6979 A.2.5: false
  • Что хеширует вызов подписи

    hashes-message

    Данные хешируются указанным вами алгоритмом — подпись прошла проверку по SHA-256 от сообщения в независимой реализации. Вызова, который подписывает заранее вычисленный дайджест, нет, поэтому передавайте само сообщение.

    Что записала проверка
    fact ecdsa-prehash = hashes-message
    evidence ecdsa-prehash: signature verifies against SHA-256(message) in @noble/curves: true
  • Хеш-функция по умолчанию

    sha256

    Если алгоритм равен null или undefined, подпись ключом EC создаётся с SHA-256 независимо от кривой. Для ключей P-384 и P-521 указывайте хеш явно.

    Что записала проверка
    fact ecdsa-default-hash = sha256
  • Некорректная подпись ECDSA

    returns-false

    Подпись raw r‖s там, где ожидается DER, DER там, где ожидается raw, и просто мусор — во всех случаях crypto.verify() возвращает false; исключений нет, поэтому проблема с форматом выглядит как неверная подпись.

    Что записала проверка
    fact ecdsa-malformed-signature = returns-false
  • Кривые ECDSA

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

    На кривых P-224, P-256, P-384, P-521, secp256k1 и Brainpool r1 сгенерировались ключи и прошёл полный цикл подписи и проверки. crypto.getCurves() перечисляет имена кривых, принятые в OpenSSL, например prime256v1 для P-256.

    Что записала проверка
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Подпись Ed25519 и Ed448 создаётся через crypto.sign(null, data, key); для Ed448 доступен и параметр context.

    Что записала проверка
    fact eddsa = Ed25519, Ed448
  • Постквантовые алгоритмы

    ML-KEMML-DSASLH-DSA

    ML-KEM-512/768/1024 (через crypto.encapsulate() и crypto.decapsulate()), ML-DSA-44/65/87 и SLH-DSA работали в Node.js 24.22.0, который поставляется с OpenSSL 3.5.8.

    Что записала проверка
    fact pqc = ML-KEM, ML-DSA, SLH-DSA
  • Форматы открытых ключей

    spki-pemspki-derx509jwksec1-uncompressedsec1-compressed

    crypto.createPublicKey() прочитала SPKI в PEM и в DER, сертификат X.509 в PEM, JWK и голую точку SEC1 — несжатую или сжатую — с format: "raw-public". 64-байтовое значение x‖y без префикса 0x04 было отклонено.

    Что записала проверка
    fact public-key-formats = spki-pem, spki-der, x509, jwk, sec1-uncompressed, sec1-compressed
  • Форматы закрытых ключей

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

    crypto.createPrivateKey() прочитала PKCS#8 в PEM и DER, PKCS#8, зашифрованный парольной фразой, SEC1 EC PRIVATE KEY в PEM и DER, JWK с d и голый 32-байтовый скаляр с format: "raw-private".

    Что записала проверка
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der, jwk, raw-scalar

Использование

Подпишите и проверьте данные на P-256, указав хеш и кодировку подписи на обеих сторонах — здесь raw r‖s для другой стороны на WebCrypto или 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);

Тестовый стенд для фрагментов кода запускает каждый пример без изменений в образе контейнера node:24.22.0-bookworm-slim, и запуск завершается ошибкой, если вывод не совпадает.

Команда
node usage.mjs
Вывод должен совпадать (регулярное выражение)
signature bytes: 64\nverified: true

Инструменты

Термины

Библиотеки

Статьи

Рецепты

Ошибки

Библиотеки