ECDH: обмен ключами на эллиптических кривых

Алгоритм Elliptic Curve Diffie–Hellman (ECDH) предназначен для безопасного согласования общего секрета между двумя сторонами по открытому каналу. В отличие от классического Diffie–Hellman, он использует арифметику эллиптических кривых, что обеспечивает сопоставимый уровень безопасности при значительно меньших размерах ключей.

Ключевые свойства ECDH:

  • каждая сторона генерирует пару ключей (приватный и публичный);
  • публичные ключи обмениваются по сети;
  • общий секрет вычисляется независимо обеими сторонами;
  • безопасность основана на сложности задачи дискретного логарифма на эллиптической кривой.

Математическая модель

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

y² = x³ + ax + b (mod p)

Где:

  • p — простое число (модуль поля),
  • a, b — параметры кривой.

Операции выполняются над точками кривой:

  • сложение точек,
  • умножение точки на скаляр.

Приватный ключ — случайное число d, публичный ключ — точка Q = dG, где G — базовая точка кривой.

Общий секрет вычисляется как:

S = d_A * Q_B = d_B * Q_A

Поддержка ECDH в библиотеке jsrsasign

Библиотека jsrsasign предоставляет инструменты для работы с эллиптическими кривыми через модуль KJUR.crypto.ECDSA. Хотя изначально он предназначен для цифровых подписей, его можно использовать и для ECDH, так как базовые операции совпадают.

Генерация ключевой пары

var ec = new KJUR.crypto.ECDSA({ curve: "secp256r1" });
var keypair = ec.generateKeyPairHex();

var privateKeyHex = keypair.ecprvhex;
var publicKeyHex = keypair.ecpubhex;

Особенности:

  • secp256r1 — одна из наиболее распространённых кривых;
  • приватный ключ представлен в виде шестнадцатеричной строки;
  • публичный ключ — это сериализованная точка.

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

var ecA = new KJUR.crypto.ECDSA({ curve: "secp256r1" });
ecA.setPrivateKeyHex(privateKeyHex);

var ecB = new KJUR.crypto.ECDSA({ curve: "secp256r1" });
ecB.setPublicKeyHex(publicKeyHex);

Методы:

  • setPrivateKeyHex — установка приватного ключа;
  • setPublicKeyHex — установка публичного ключа.

Вычисление общего секрета

В jsrsasign отсутствует прямой метод ECDH, но можно воспользоваться внутренними операциями:

function deriveSharedSecret(privKeyHex, pubKeyHex) {
    var ec = new KJUR.crypto.ECDSA({ curve: "secp256r1" });
    ec.setPrivateKeyHex(privKeyHex);

    var pubKey = ec.ecparams.curve.decodePointHex(pubKeyHex);
    var d = new BigInteger(privKeyHex, 16);

    var sharedPoint = pubKey.multiply(d);
    return sharedPoint.getX().toBigInteger().toString(16);
}

Ключевые шаги:

  1. Декодирование публичной точки;
  2. Умножение точки на приватный скаляр;
  3. Использование координаты X как общего секрета.

Пример обмена ключами

// Сторона A
var ecA = new KJUR.crypto.ECDSA({ curve: "secp256r1" });
var keyA = ecA.generateKeyPairHex();

// Сторона B
var ecB = new KJUR.crypto.ECDSA({ curve: "secp256r1" });
var keyB = ecB.generateKeyPairHex();

// Обмен публичными ключами
var sharedA = deriveSharedSecret(keyA.ecprvhex, keyB.ecpubhex);
var sharedB = deriveSharedSecret(keyB.ecprvhex, keyA.ecpubhex);

console.log(sharedA === sharedB); // true

Результат:

  • обе стороны получают одинаковый секрет;
  • секрет не передаётся по сети.

Преобразование секрета в ключ

Полученный секрет обычно не используется напрямую. Он проходит через KDF (Key Derivation Function).

var sharedHex = sharedA;
var sharedBytes = hextorstr(sharedHex);

var hash = KJUR.crypto.Util.sha256(sharedBytes);

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

  • симметричное шифрование (AES);
  • HMAC;
  • генерация ключей сессии.

Форматы ключей

Поддерживаются различные форматы:

  • HEX — основной формат внутри библиотеки;
  • PEM — для хранения и передачи;
  • DER — бинарный формат ASN.1.

Пример преобразования:

var pubKeyObj = KEYUTIL.getKey({
    xy: publicKeyHex,
    curve: "secp256r1"
});

Выбор эллиптической кривой

Распространённые варианты:

  • secp256r1 (P-256) — стандарт NIST;
  • secp384r1 — повышенная безопасность;
  • secp521r1 — максимальная защита;
  • secp256k1 — используется в блокчейн-системах.

Влияние выбора:

  • размер ключа;
  • производительность;
  • совместимость.

Безопасность реализации

Критические аспекты:

  • приватный ключ должен быть случайным и защищённым;
  • необходимо использовать проверенные кривые;
  • защита от атак по времени (timing attacks);
  • проверка корректности публичных ключей.

Проверка точки:

if (!ec.ecparams.curve.validate(pubKey)) {
    throw new Error("Invalid public key");
}

Частые ошибки

1. Использование сырых координат без KDF Приводит к уязвимостям при шифровании.

2. Повторное использование приватного ключа Снижает криптографическую стойкость.

3. Неправильная сериализация ключей Может привести к несовместимости между системами.

4. Игнорирование проверки публичного ключа Открывает возможность атак.

Оптимизация

  • использование WebCrypto API при возможности;
  • кеширование параметров кривой;
  • минимизация преобразований форматов.

Сравнение с классическим Diffie–Hellman

Характеристика DH ECDH
Размер ключа большой маленький
Производительность ниже выше
Уровень безопасности одинаковый одинаковый
Применение устаревает активно используется

Интеграция с другими алгоритмами

ECDH часто используется совместно с:

  • AES — для симметричного шифрования;
  • HMAC — для аутентификации;
  • HKDF — для безопасного вывода ключей.

Пример:

var aesKey = KJUR.crypto.Util.sha256(sharedHex).substr(0, 32);

Практические сценарии

  • TLS (HTTPS соединения);
  • защищённые мессенджеры;
  • обмен ключами в IoT;
  • криптовалютные протоколы.

Ограничения jsrsasign

  • отсутствует нативная функция ECDH;
  • требуется ручная реализация;
  • уступает WebCrypto по производительности;
  • не всегда удобна работа с бинарными форматами.

Тем не менее, библиотека остаётся мощным инструментом для:

  • прототипирования;
  • обучения;
  • серверных решений на Node.js.