ECDSA: подпись на эллиптических кривых

ECDSA (Elliptic Curve Digital Signature Algorithm) основан на алгебраической структуре эллиптических кривых над конечными полями. В отличие от классических схем RSA, безопасность здесь опирается не на факторизацию больших чисел, а на сложность задачи дискретного логарифмирования в группе точек эллиптической кривой.

Эллиптическая кривая задаётся уравнением вида:

  • ( y^2 = x^3 + ax + b )

над конечным полем ( _p ), где параметры ( a ) и ( b ) определяют конкретную кривую, а условие невырожденности гарантирует отсутствие сингулярностей:

  • ( 4a^3 + 27b^2 )

Точки на кривой вместе с операцией сложения формируют абелеву группу. В этой группе и строится криптографическая конструкция.

ECDSA использует идею:

  • закрытый ключ — случайное число ( d )
  • открытый ключ — точка ( Q = dG ), где ( G ) — базовая точка (generator point)

Обратное вычисление ( d ) из ( Q ) считается вычислительно невозможным при правильно выбранных параметрах кривой.

Используемые кривые и параметры

На практике применяются стандартизированные кривые:

  • secp256k1 (используется в Bitcoin и многих блокчейн-системах)
  • P-256 (prime256v1, широко используется в TLS)
  • P-384 и P-521 для повышенной стойкости

Каждая кривая задаёт:

  • порядок группы ( n )
  • базовую точку ( G )
  • коэффициенты уравнения кривой
  • модуль поля ( p )

В Jsrsasign выбор кривой определяется строковым идентификатором:

"secp256k1"
"prime256v1"
"P-256"

Базовый принцип цифровой подписи ECDSA

Схема подписи включает три этапа: генерация ключей, создание подписи и её проверка.

Генерация ключей

Закрытый ключ:

  • случайное число ( d )

Открытый ключ:

  • точка ( Q = dG )

В Jsrsasign генерация выполняется через KEYUTIL:

const kp = KEYUTIL.generateKeypair("EC", "secp256k1");

const priv = kp.prvKeyObj;
const pub = kp.pubKeyObj;

Либо через импорт PEM:

const privateKey = KEYUTIL.getKey(pemPrivateKey);
const publicKey = KEYUTIL.getKey(pemPublicKey);

Процесс создания подписи

Подпись сообщения состоит из пары чисел ( (r, s) ).

1. Хеширование сообщения

Сообщение сначала проходит через криптографическую хеш-функцию:

  • SHA-256 (наиболее распространённый вариант)
const msgHash = KJUR.crypto.Util.sha256("message");

2. Генерация случайного числа k

Каждая подпись требует уникального случайного числа ( k ):

  • критически важный элемент безопасности
  • повторное использование ( k ) приводит к компрометации закрытого ключа

3. Вычисление r

  • ( R = kG )
  • ( r = x_R n )

4. Вычисление s

  • ( s = k^{-1}(H(m) + dr) n )

Где:

  • ( H(m) ) — хеш сообщения
  • ( d ) — закрытый ключ

Подписание данных в Jsrsasign

Библиотека скрывает математические операции и предоставляет высокоуровневый API.

Подпись через ECDSA класс

const sig = new KJUR.crypto.Signature({ "alg": "SHA256withECDSA" });

sig.init(privateKey);
sig.updateString("message");

const signature = sig.sign();

Результат обычно возвращается в формате DER (ASN.1 структура).

Подпись с явным указанием кривой

const sig = new KJUR.crypto.Signature({
  alg: "SHA256withECDSA",
  prov: "cryptojs/jsrsa"
});

sig.init(privateKey);
sig.updateString("data");

const sigHex = sig.sign();

Форматы представления подписи

ECDSA подпись может быть представлена в нескольких форматах.

DER (ASN.1)

Наиболее распространённый формат:

SEQUENCE {
  r INTEGER,
  s INTEGER
}

Jsrsasign по умолчанию использует DER:

const signatureHex = sig.sign(); // DER в hex

RAW формат (r || s)

Иногда используется в протоколах:

const sig = new KJUR.crypto.Signature({
  alg: "SHA256withECDSA",
  format: "RAW"
});

Проверка подписи

Проверка выполняется с использованием открытого ключа:

Математическая идея проверки

Проверяется равенство:

  • ( r x( (H(m)s^{-1})G + (rs^{-1})Q ) n )

Если равенство выполняется, подпись считается корректной.

Проверка в Jsrsasign

const sig = new KJUR.crypto.Signature({ "alg": "SHA256withECDSA" });

sig.init(publicKey);
sig.updateString("message");

const isValid = sig.verify(signature);

Результат:

  • true — подпись корректна
  • false — подпись нарушена или ключ не соответствует

Работа с ключами EC в Jsrsasign

Импорт PEM ключей

const pemPrivate = `
-----BEGIN EC PRIVATE KEY-----
...
-----END EC PRIVATE KEY-----
`;

const pemPublic = `
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
`;

const privKey = KEYUTIL.getKey(pemPrivate);
const pubKey = KEYUTIL.getKey(pemPublic);

Экспорт ключей

const pem = KEYUTIL.getPEM(privKey, "PKCS8PRV");

Взаимодействие с криптографическими провайдерами

Jsrsasign может использовать разные backend-реализации:

  • native JavaScript
  • WebCrypto (в браузерах)
  • CryptoJS

Конфигурация:

const sig = new KJUR.crypto.Signature({
  alg: "SHA256withECDSA",
  prov: "cryptojs/jsrsa"
});

Особенности безопасности ECDSA

Ключевые свойства, определяющие устойчивость схемы:

Уникальность nonce k

Повторное использование ( k ) приводит к раскрытию закрытого ключа:

  • атака через решение системы линейных уравнений
  • известные реальные инциденты в криптосистемах

Сильная случайность

Генерация ( k ) должна быть криптографически стойкой.

Выбор хеша

Используются:

  • SHA-256
  • SHA-384
  • SHA-512

Слабые хеш-функции снижают стойкость всей схемы.

Пример полного цикла работы

const kp = KEYUTIL.generateKeypair("EC", "secp256k1");

const priv = kp.prvKeyObj;
const pub = kp.pubKeyObj;

const sig = new KJUR.crypto.Signature({
  alg: "SHA256withECDSA"
});

sig.init(priv);
sig.updateString("transaction data");

const signature = sig.sign();

const verifier = new KJUR.crypto.Signature({
  alg: "SHA256withECDSA"
});

verifier.init(pub);
verifier.updateString("transaction data");

const result = verifier.verify(signature);

Отличия от RSA подписи

ECDSA отличается от RSA следующими свойствами:

  • меньший размер ключа при равной стойкости
  • более высокая производительность на современных системах
  • сложность реализации из-за работы с эллиптическими кривыми
  • зависимость безопасности от качества генерации случайных чисел

Использование в протоколах

ECDSA применяется в:

  • TLS сертификатах
  • блокчейн-системах (Bitcoin, Ethereum)
  • JWT (ES256, ES384, ES512)
  • криптографических API

В JWT форматах Jsrsasign часто используется для ES256:

const jwt = KJUR.jws.JWS.sign(
  "ES256",
  { alg: "ES256" },
  { data: "payload" },
  privateKey
);

Внутренние структуры Jsrsasign

Библиотека включает несколько ключевых компонентов:

  • KJUR.crypto.Signature — основной класс подписи
  • KEYUTIL — работа с ключами
  • KJUR.crypto.ECDSA — низкоуровневая реализация алгоритма
  • KJUR.asn1 — работа с ASN.1 структурами

Низкоуровневое использование ECDSA

const ecdsa = new KJUR.crypto.ECDSA({ curve: "secp256k1" });

const sigObj = ecdsa.signHex(hashHex, privateKeyHex);
const valid = ecdsa.verifyHex(hashHex, sigObj, publicKeyHex);

Формирование ошибок и проверка корректности

Типичные ошибки:

  • неверный формат ключа
  • несоответствие кривой
  • повреждённая DER структура подписи
  • неправильный алгоритм хеширования

Jsrsasign возвращает false при неудачной проверке, но не всегда выбрасывает исключения, что требует явной проверки входных данных.

Применение в прикладных сценариях

ECDSA используется в:

  • подписании транзакций
  • верификации API запросов
  • защите сообщений между сервисами
  • цифровых сертификатах

В каждом случае ключевым элементом остаётся проверяемая криптографическая гарантия подлинности данных без раскрытия закрытого ключа.