ECDH-ES относится к классу алгоритмов согласования ключей в спецификации JWE (JSON Web Encryption) и используется библиотекой Jose для реализации гибридного шифрования на основе эллиптической криптографии. Основная идея заключается в том, что отправитель и получатель вычисляют общий секретный ключ через ECDH (Elliptic Curve Diffie-Hellman), после чего из этого секрета выводится ключ шифрования содержимого (CEK — Content Encryption Key).
ECDH-ES работает без передачи симметрического ключа по сети. Вместо этого используется эпhemeral-ключ отправителя и публичный ключ получателя, что делает схему устойчивой к перехвату трафика и обеспечивает прямую forward secrecy при корректной реализации.
В JWE алгоритм alg определяет способ защиты CEK. В
случае ECDH-ES:
Структура JWE при ECDH-ES:
protected header содержит alg: "ECDH-ES"
или расширенные варианты;enc задаёт симметрический алгоритм шифрования контента
(например, A256GCM);epk (ephemeral public key) передаётся вместе с
сообщением;apu и apv — дополнительные параметры
согласования ключа (optional context).Процесс согласования ключа можно разложить на несколько этапов:
Генерация ephemeral key pair отправителем Отправитель создаёт временную пару ключей на эллиптической кривой.
Использование публичного ключа получателя Публичный ключ получателя используется вместе с ephemeral private key отправителя.
Вычисление shared secret Оба участника независимо вычисляют общий секрет через ECDH.
Derivation CEK через Concat KDF Из shared secret выводится CEK с использованием параметров:
Шифрование полезной нагрузки CEK используется с симметрическим алгоритмом (например AES-GCM).
Ключевая особенность ECDH-ES заключается в том, что CEK не инкапсулируется, а выводится напрямую.
Библиотека Jose предоставляет высокоуровневые API для работы с JWE.
import { generateKeyPair } from 'jose'
const { publicKey, privateKey } = await generateKeyPair('ECDH-ES', {
crv: 'P-256'
})
Здесь создаётся ключевая пара на кривой P-256.
import { CompactEncrypt } from 'jose'
const encoder = new TextEncoder()
const jwe = await new CompactEncrypt(
encoder.encode('Secret message')
)
.setProtectedHeader({
alg: 'ECDH-ES',
enc: 'A256GCM'
})
.encrypt(publicKey)
В этом случае:
alg: ECDH-ES — способ согласования ключа;enc: A256GCM — симметрическое шифрование данных.import { compactDecrypt } from 'jose'
const { plaintext } = await compactDecrypt(jwe, privateKey)
console.log(new TextDecoder().decode(plaintext))
При расшифровании происходит:
В чистом ECDH-ES CEK не передаётся и не шифруется отдельно. Он всегда производный:
encЭто делает схему более компактной, но накладывает ограничения:
Расширенный вариант алгоритма — ECDH-ES+A128KW,
ECDH-ES+A192KW, ECDH-ES+A256KW.
В отличие от базового ECDH-ES:
import { generateKeyPair } from 'jose'
const { publicKey, privateKey } = await generateKeyPair('ECDH-ES+A256KW', {
crv: 'P-256'
})
import { CompactEncrypt } from 'jose'
const jwe = await new CompactEncrypt(
new TextEncoder().encode('Top secret data')
)
.setProtectedHeader({
alg: 'ECDH-ES+A256KW',
enc: 'A256GCM'
})
.encrypt(publicKey)
import { compactDecrypt } from 'jose'
const { plaintext } = await compactDecrypt(jwe, privateKey)
console.log(new TextDecoder().decode(plaintext))
Внутри реализации Jose:
deriveKey через Concat KDFОба варианта используют KDF на основе:
Функция обеспечивает детерминированное и безопасное получение ключей.
ECDH-ES обеспечивает следующие свойства:
ECDH-ES+AES-KW добавляет:
ECDH-ES предпочтителен, когда:
ECDH-ES+AES-KW используется, когда:
enc (например несовместимость с
ключом)Типичный компактный JWE:
header.
encrypted_key.
iv.
ciphertext.
tag
Для ECDH-ES поле encrypted_key часто пустое, так как CEK
не шифруется напрямую.
Для ECDH-ES+A256KW:
encrypted_key содержит CEK, зашифрованный AES-KW.ECDH-ES в Jose используется в:
ECDH-ES+AES-KW чаще встречается в: