Типы ключей: CryptoKey и CryptoKeyPair

В Web Crypto API криптографические операции строятся вокруг объекта CryptoKey, который представляет собой абстракцию над криптографическим ключом, управляемым браузером или средой выполнения. Этот объект не содержит «сырого» ключевого материала в явном виде в JavaScript-коде, если только он не был специально экспортирован.

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

Структура CryptoKey

Объект CryptoKey не является обычным объектом с набором методов для работы с данными. Он содержит только метаданные, необходимые для работы криптографического движка:

  • type — тип ключа ("secret", "public", "private")
  • extractable — возможность экспорта ключа
  • algorithm — описание алгоритма, для которого предназначен ключ
  • usages — массив допустимых операций

type

Поле type определяет природу ключа:

  • "secret" — симметричный ключ (один ключ для шифрования и дешифрования)
  • "public" — публичная часть асимметричной пары
  • "private" — приватная часть асимметричной пары

Это поле критически важно, так как браузер строго ограничивает операции в зависимости от типа ключа.

extractable

Свойство extractable определяет, можно ли извлечь ключевой материал:

  • true — ключ может быть экспортирован (например, в формате JWK или raw)
  • false — ключ остается внутри криптографического контекста и не может быть получен в виде байтов

В большинстве безопасных сценариев приватные ключи создаются с extractable: false, чтобы исключить утечку.

algorithm

Поле algorithm описывает криптографический алгоритм, связанный с ключом. Оно может содержать параметры, зависящие от конкретного алгоритма.

Примеры:

  • "AES-GCM" — симметричное шифрование
  • "RSA-OAEP" — асимметричное шифрование
  • "ECDSA" — цифровая подпись
  • "ECDH" — обмен ключами

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

usages

Массив usages определяет, для каких операций ключ разрешен:

  • "encrypt" — шифрование
  • "decrypt" — дешифрование
  • "sign" — создание подписи
  • "verify" — проверка подписи
  • "deriveKey" — вывод нового ключа
  • "deriveBits" — получение битового материала
  • "wrapKey" — упаковка ключа
  • "unwrapKey" — распаковка ключа

Если попытаться использовать ключ не по назначению, операция завершится ошибкой, даже если сам ключ корректен.


Создание CryptoKey через SubtleCrypto

Ключи не создаются напрямую через конструктор. Вместо этого используется crypto.subtle.

Пример генерации симметричного ключа:

const key = await crypto.subtle.generateKey(
  {
    name: "AES-GCM",
    length: 256
  },
  true,
  ["encrypt", "decrypt"]
);

Результатом будет объект CryptoKey.


CryptoKeyPair как пара ключей

Для асимметричной криптографии используется структура CryptoKeyPair, которая объединяет два объекта CryptoKey:

  • publicKey
  • privateKey

Эта структура возвращается, например, при генерации RSA или ECDSA ключей.

Пример CryptoKeyPair

const keyPair = await crypto.subtle.generateKey(
  {
    name: "RSA-OAEP",
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256"
  },
  true,
  ["encrypt", "decrypt"]
);

Результат:

{
  publicKey: CryptoKey,
  privateKey: CryptoKey
}

Логическая связь ключей

В отличие от симметричного ключа, где один объект выполняет обе функции, CryptoKeyPair разделяет обязанности:

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

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

CryptoKeyPair не является отдельным классом — это структурированный объект. Он не обладает методами и существует только как результат работы generateKey.

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

  • нельзя создать вручную через new
  • нельзя модифицировать типы ключей внутри пары
  • каждый ключ в паре имеет собственные usages
  • extractable может различаться для public и private ключа

Поведение ключей в памяти

CryptoKey и CryptoKeyPair не дают доступа к сырому байтовому представлению ключа в оперативной памяти. Это сделано для повышения безопасности.

Даже если ключ используется в нескольких операциях:

  • браузер хранит его в защищенном контексте
  • JavaScript-код получает только ссылку на объект
  • прямой доступ к криптографическим материалам невозможен

Импорт и экспорт CryptoKey

Несмотря на изоляцию, ключи могут быть сериализованы при условии, что extractable: true.

Экспорт

const exported = await crypto.subtle.exportKey("jwk", key);

Возможные форматы:

  • "raw"
  • "jwk"
  • "spki"
  • "pkcs8"

Импорт

const key = await crypto.subtle.importKey(
  "jwk",
  jwkData,
  {
    name: "AES-GCM"
  },
  true,
  ["encrypt", "decrypt"]
);

При импорте создается новый CryptoKey, который снова становится управляемым объектом Web Crypto API.


Связь CryptoKey с алгоритмами

Каждый CryptoKey жестко связан с конкретным криптографическим алгоритмом. Например:

  • AES ключ нельзя использовать в RSA операциях
  • ECDSA ключ нельзя использовать для шифрования AES-GCM

Это обеспечивает строгую типизацию на уровне криптографического API.

Внутри algorithm хранится информация, позволяющая движку:

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

Использование ключей в операциях SubtleCrypto

CryptoKey используется как входной параметр во всех основных методах subtle:

  • encrypt
  • decrypt
  • sign
  • verify
  • deriveKey
  • deriveBits

Пример:

const encrypted = await crypto.subtle.encrypt(
  {
    name: "AES-GCM",
    iv
  },
  key,
  data
);

Здесь key — это CryptoKey, а не массив байтов.


Жизненный цикл CryptoKey

Типичный жизненный цикл включает несколько этапов:

  1. Генерация или импорт
  2. Использование в криптографических операциях
  3. Возможный экспорт (если разрешено)
  4. Удаление из памяти браузером

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


Безопасностные аспекты

CryptoKey и CryptoKeyPair проектировались как средство минимизации утечек ключей.

Основные защитные механизмы:

  • отсутствие прямого доступа к байтам ключа
  • ограничение операций через usages
  • изоляция в криптографическом контексте браузера
  • невозможность сериализации при extractable: false

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


Отличия CryptoKey от обычных структур данных

CryptoKey принципиально отличается от типичных JavaScript объектов:

  • не является plain object
  • не сериализуется через JSON
  • не клонируется через structured clone в полном виде
  • имеет внутреннюю привязку к криптографическому провайдеру

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


Роль CryptoKeyPair в асимметричных системах

CryptoKeyPair используется в сценариях:

  • TLS-подобных протоколах на уровне приложения
  • цифровых подписях документов
  • обмене ключами (ECDH)
  • OAuth-подобных схемах с подписью запросов

Разделение на public/private ключи обеспечивает:

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

Поведение при ошибках использования

Если CryptoKey используется некорректно, возникают типичные ошибки:

  • InvalidAccessError — ключ не поддерживает операцию
  • OperationError — алгоритм не может выполнить операцию
  • DataError — некорректные входные данные

Причина почти всегда связана с несовпадением:

  • алгоритма
  • usages
  • типа ключа

Взаимодействие с Web Crypto контекстом

CryptoKey существует только внутри контекста window.crypto или self.crypto (в Web Worker). Перенос ключей между контекстами возможен только через structured clone, и только если это разрешено свойством extractable.


Ограничения архитектуры

Модель CryptoKey накладывает жесткие ограничения:

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

Это делает API безопасным, но менее гибким по сравнению с низкоуровневыми криптобиблиотеками.