Метод subtle.deriveKey через ECDH

ECDH (Elliptic Curve Diffie–Hellman) в Web Crypto API используется для выработки общего секрета между двумя сторонами без передачи секретного значения по сети. Метод crypto.subtle.deriveKey позволяет не только получить сырое значение общего секрета, но и сразу преобразовать его в ключ для симметричного шифрования, например AES-GCM или AES-CBC.

ECDH в контексте Web Crypto реализуется через объектный интерфейс ключей, где публичная и приватная части строго разделены и управляются браузером или средой исполнения.


Базовая схема ECDH через WebCrypto

Процесс обмена ключами состоит из нескольких этапов:

  • генерация пары ключей ECDH для каждой стороны
  • обмен публичными ключами
  • использование приватного ключа и публичного ключа другой стороны для вывода общего ключа
  • производное создание симметричного ключа через deriveKey

Ключевой момент: общий секрет напрямую не используется в приложении, он превращается в криптографический ключ для выбранного алгоритма.


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

ECDH в Web Crypto требует использования именованных кривых, чаще всего P-256, P-384 или P-521.

const aliceKeys = await crypto.subtle.generateKey(
  {
    name: "ECDH",
    namedCurve: "P-256"
  },
  true,
  ["deriveKey"]
);

const bobKeys = await crypto.subtle.generateKey(
  {
    name: "ECDH",
    namedCurve: "P-256"
  },
  true,
  ["deriveKey"]
);

Каждый объект содержит:

  • privateKey — секретная часть
  • publicKey — открытая часть

Экспорт и обмен публичными ключами

Для того чтобы стороны могли использовать ECDH, публичный ключ необходимо передать другой стороне.

const alicePublic = await crypto.subtle.exportKey(
  "raw",
  aliceKeys.publicKey
);

const bobPublic = await crypto.subtle.exportKey(
  "raw",
  bobKeys.publicKey
);

Далее данные передаются через сеть, WebSocket, HTTP или любой другой канал.

После получения они импортируются обратно:

const importedBobPublic = await crypto.subtle.importKey(
  "raw",
  bobPublic,
  {
    name: "ECDH",
    namedCurve: "P-256"
  },
  true,
  []
);

Метод subtle.deriveKey с ECDH

Основная операция происходит через deriveKey. Она принимает приватный ключ одной стороны и публичный ключ другой стороны.

Сигнатура:

crypto.subtle.deriveKey(
  algorithm,
  baseKey,
  derivedKeyType,
  extractable,
  keyUsages
)

Параметры ECDH

Для ECDH:

  • algorithm:
{
  name: "ECDH",
  public: CryptoKey
}
  • baseKey: приватный ключ ECDH
  • derivedKeyType: алгоритм результата (например AES-GCM)
  • extractable: можно ли извлечь ключ
  • keyUsages: назначение ключа (encrypt, decrypt и т.д.)

Пример получения общего AES-ключа

const sharedKeyAlice = await crypto.subtle.deriveKey(
  {
    name: "ECDH",
    public: importedBobPublic
  },
  aliceKeys.privateKey,
  {
    name: "AES-GCM",
    length: 256
  },
  false,
  ["encrypt", "decrypt"]
);

Аналогично на стороне B:

const sharedKeyBob = await crypto.subtle.deriveKey(
  {
    name: "ECDH",
    public: alicePublicKeyImported,
  },
  bobKeys.privateKey,
  {
    name: "AES-GCM",
    length: 256
  },
  false,
  ["encrypt", "decrypt"]
);

Результат:

  • sharedKeyAlice и sharedKeyBob будут одинаковыми
  • напрямую секрет не передаётся
  • используется только комбинация приватного и публичного ключа

Использование полученного ключа

После деривации ключ можно использовать как обычный симметричный ключ:

const iv = crypto.getRandomValues(new Uint8Array(12));

const encrypted = await crypto.subtle.encrypt(
  {
    name: "AES-GCM",
    iv
  },
  sharedKeyAlice,
  new TextEncoder().encode("секретное сообщение")
);

Расшифрование:

const decrypted = await crypto.subtle.decrypt(
  {
    name: "AES-GCM",
    iv
  },
  sharedKeyBob,
  encrypted
);

Отличие deriveKey от deriveBits

В ECDH Web Crypto API доступны два подхода:

  • deriveBits — возвращает «сырой» массив байтов общего секрета
  • deriveKey — сразу создаёт CryptoKey для симметричного алгоритма
const rawBits = await crypto.subtle.deriveBits(
  {
    name: "ECDH",
    public: importedBobPublic
  },
  aliceKeys.privateKey,
  256
);

deriveKey предпочтительнее, потому что:

  • исключает ручную обработку сырого секрета
  • снижает риск утечек через память
  • сразу интегрируется с AES и другими алгоритмами

Параметры derivedKeyType

Чаще всего используются:

  • AES-GCM (рекомендуется)
  • AES-CBC
  • AES-KW (для ключевой упаковки)

Пример:

{
  name: "AES-GCM",
  length: 256
}

Важно: длина ключа должна соответствовать требованиям алгоритма.


Типичные ошибки при работе с ECDH

Несовпадение кривой

Обе стороны обязаны использовать одну и ту же кривую:

  • P-256 ≠ P-384
  • несовпадение приводит к ошибке deriveKey

Неверный тип ключа

deriveKey требует:

  • baseKey: CryptoKey с usage "deriveKey"
  • publicKey: ECDH public key

Попытка извлечь секрет

Если extractable: false, ключ нельзя экспортировать — это нормальное поведение для защиты.


Безопасные практики использования ECDH

ECDH в Web Crypto безопасен при соблюдении базовых условий:

  • использование современных кривых (P-256 и выше)
  • обязательное применение AES-GCM для шифрования данных
  • запрет на экспорт приватных ключей
  • генерация уникальных IV для каждого шифрования
  • защита канала передачи публичных ключей (TLS)

Дополнительно:

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

Архитектура типичного ECDH-обмена

Сценарий взаимодействия обычно выглядит так:

  • клиент A генерирует ключевую пару ECDH
  • клиент B генерирует ключевую пару ECDH
  • публичные ключи обмениваются через сервер
  • обе стороны выполняют deriveKey
  • полученный AES-ключ используется для дальнейшего шифрования сообщений

Такой подход обеспечивает сквозное шифрование без передачи симметричного ключа.


Важные особенности реализации в браузерах

  • операции WebCrypto асинхронные и используют промисы
  • ключи привязаны к контексту происхождения (origin)
  • доступ к API возможен только через secure context (HTTPS)
  • производительность ECDH зависит от аппаратного ускорения

Поведение deriveKey при повторном вызове

Если повторно вызвать deriveKey с теми же ключами:

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

Связка ECDH и KDF-подходов

Хотя WebCrypto не требует ручного KDF, ECDH можно рассматривать как базу для дальнейшего вывода ключей:

  • ECDH → общий секрет
  • deriveKey → AES ключ
  • AES-GCM → защита сообщений

В более сложных системах поверх ECDH могут использоваться дополнительные слои (например, протоколы наподобие Signal), но WebCrypto скрывает часть этой сложности внутри deriveKey.