Алгоритм ECDH (Elliptic Curve Diffie–Hellman) реализует механизм согласования общего секрета между двумя сторонами через небезопасный канал. Основан на свойствах эллиптических кривых, где вычисление скалярного произведения точки считается простой операцией, а обратная задача (нахождение множителя) — вычислительно сложной.
Каждая сторона:
Ключевое свойство:
PrivA * PubB = PrivB * PubA = SharedSecret
Библиотека jsrsasign предоставляет низкоуровневые и высокоуровневые API для работы с эллиптическими кривыми:
KEYUTIL.generateKeypairKJUR.crypto.ECDSAECDH через методы работы с
ключамиПоддерживаются популярные кривые:
Для каждой стороны создаётся независимая пара ключей.
const kpA = KEYUTIL.generateKeypair("EC", "secp256r1");
const kpB = KEYUTIL.generateKeypair("EC", "secp256r1");
Результат:
kpA.prvKeyObj — приватный ключ AkpA.pubKeyObj — публичный ключ AПубличный ключ на эллиптической кривой представлен точкой (x, y):
const pubA = kpA.pubKeyObj.pubKeyHex;
const pubB = kpB.pubKeyObj.pubKeyHex;
Этот формат используется для передачи между сторонами.
jsrsasign не имеет отдельного высокоуровневого метода
computeSecret, но позволяет выполнить вычисление через
внутренние структуры.
Пример вычисления секрета стороной A:
const ecA = new KJUR.crypto.ECDSA({curve: "secp256r1"});
ecA.setPrivateKeyHex(kpA.prvKeyObj.prvKeyHex);
const sharedA = ecA.ecdh(kpB.pubKeyObj.pubKeyHex);
Аналогично для стороны B:
const ecB = new KJUR.crypto.ECDSA({curve: "secp256r1"});
ecB.setPrivateKeyHex(kpB.prvKeyObj.prvKeyHex);
const sharedB = ecB.ecdh(kpA.pubKeyObj.pubKeyHex);
console.log(sharedA === sharedB); // true
Совпадение подтверждает корректность рукопожатия.
Полученный общий секрет — это сырые данные. Для практического применения требуется преобразование через KDF (Key Derivation Function).
Пример с SHA-256:
const hashA = KJUR.crypto.Util.hashHex(sharedA, "sha256");
Результат:
После получения ключа можно применить симметричное шифрование:
const key = hashA.substring(0, 32); // 128 бит для AES
const encrypted = KJUR.crypto.Cipher.encrypt(
"Secret message",
key,
"aes128-CBC"
);
Расшифрование:
const decrypted = KJUR.crypto.Cipher.decrypt(
encrypted,
key,
"aes128-CBC"
);
jsrsasign поддерживает несколько форматов:
Экспорт публичного ключа:
const pemPub = KEYUTIL.getPEM(kpA.pubKeyObj);
Импорт:
const pubKeyObj = KEYUTIL.getKey(pemPub);
Кривая влияет на:
Рекомендации:
secp256r1 — балансsecp384r1 — повышенная безопасностьsecp521r1 — максимальная защитаПриватные ключи должны генерироваться криптографически стойким
генератором. jsrsasign использует встроенные механизмы, но в браузере
важно наличие window.crypto.
Перед использованием необходимо убедиться:
ECDH сам по себе не защищает от MITM-атаки. Требуется:
Типичная схема:
Пример:
const sig = new KJUR.crypto.Signature({alg: "SHA256withECDSA"});
sig.init(kpA.prvKeyObj);
sig.updateString(pubA);
const signature = sig.sign();
Проверка:
const verifier = new KJUR.crypto.Signature({alg: "SHA256withECDSA"});
verifier.init(kpA.pubKeyObj);
verifier.updateString(pubA);
verifier.verify(signature);
ECDH используется в:
Типичные проблемы:
Несовпадение секрета
Ошибка при вычислении
Пустой результат
Для более корректного derivation:
function hkdf(hash, salt, ikm, info, length) {
// реализация HKDF поверх HMAC
}
Использование HKDF позволяет:
| Параметр | DH | ECDH |
|---|---|---|
| Размер ключа | большой | компактный |
| Производительность | ниже | выше |
| Безопасность | высокая | выше при равных размерах |
ECDH стал стандартом благодаря:
Особенно широко используется в варианте ECDHE (ephemeral), где ключи генерируются на каждую сессию, обеспечивая Perfect Forward Secrecy.
generateKeypairpubKeyHexecdh()hashHex()Такая последовательность формирует полноценное криптографическое рукопожатие на основе эллиптических кривых.