Форматы импорта: raw, pkcs8, spki, jwk

Форматы импорта в Web Crypto API определяют способ передачи ключевого материала в криптографическое хранилище браузера через SubtleCrypto.importKey. От выбранного формата зависит структура входных данных, область применения ключа и совместимость с внешними системами, включая серверные реализации OpenSSL, JWT-инфраструктуры и аппаратные криптомодули.

В Web Crypto API ключи не создаются и не используются в «сыром» виде без явного описания формата. Каждый формат отражает конкретную криптографическую модель: симметричное шифрование, асимметричную криптографию или представление ключа в стандартизированном обменном виде.


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

Чаще всего raw применяется для алгоритмов, где ключ — это просто набор случайных байтов фиксированной длины.

Типичные сценарии использования:

  • AES (GCM, CBC, CTR)
  • HMAC

Структура данных:

  • ArrayBuffer или TypedArray
  • фиксированная длина в зависимости от алгоритма (например, 256 бит для AES-256)

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

const keyData = crypto.getRandomValues(new Uint8Array(32));

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

Особенности формата:

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

Ключи raw не используются в асимметричных алгоритмах, так как не содержат математической структуры (например, модулей или экспонент).


PKCS#8

Формат pkcs8 применяется для импорта приватных ключей асимметричной криптографии. Это стандартизированная структура, определённая в спецификации PKCS#8, которая описывает приватный ключ вместе с алгоритмом его использования.

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

  • RSA приватных ключей
  • ECDSA/ECDH приватных ключей

Формат включает:

  • идентификатор алгоритма
  • сам приватный ключ
  • параметры алгоритма (если применимо)
  • возможное шифрование контейнера (вне WebCrypto, обычно на уровне PEM)

В WebCrypto передаётся в виде DER-кодированного ArrayBuffer.

Пример импорта:

const privateKey = await crypto.subtle.importKey(
  "pkcs8",
  pkcs8Buffer,
  {
    name: "RSA-PSS",
    hash: "SHA-256"
  },
  false,
  ["sign"]
);

Структурные особенности:

  • бинарный ASN.1 DER формат
  • содержит полный контекст ключа
  • используется только для приватных ключей
  • часто извлекается из PEM через Base64-декодирование

PEM-представление обычно выглядит так:

-----BEGIN PRIVATE KEY-----
(base64)
-----END PRIVATE KEY-----

При обработке в WebCrypto заголовки и переносы строк удаляются, остаётся чистый бинарный буфер.


SPKI

Формат spki (Subject Public Key Info) используется для импорта публичных ключей асимметричной криптографии. Он является зеркальным по отношению к PKCS#8, но предназначен исключительно для открытых ключей.

Применяется в:

  • RSA публичных ключах
  • ECDSA/ECDH публичных ключах

Структура включает:

  • идентификатор алгоритма
  • сам публичный ключ
  • параметры кривой (для ECC)

Импорт осуществляется через SubtleCrypto.importKey:

const publicKey = await crypto.subtle.importKey(
  "spki",
  spkiBuffer,
  {
    name: "RSA-OAEP",
    hash: "SHA-256"
  },
  false,
  ["encrypt"]
);

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

  • DER-кодированный ASN.1 объект
  • используется только для публичных ключей
  • широко применяется в TLS-сертификатах
  • совместим с X.509 инфраструктурой

PEM-представление:

-----BEGIN PUBLIC KEY-----
(base64)
-----END PUBLIC KEY-----

SPKI часто используется при загрузке ключей из сертификатов, извлечённых из TLS-цепочек.


JWK

Формат jwk (JSON Web Key) представляет ключ в виде JSON-объекта. Это высокоуровневое представление, стандартизированное в RFC 7517 и активно используемое в веб-экосистеме.

JWK поддерживает как симметричные, так и асимметричные ключи.

Пример RSA ключа:

{
  "kty": "RSA",
  "e": "AQAB",
  "n": "0vx7agoebGcQSuuPiLJXZptN..."
}

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

{
  "kty": "oct",
  "k": "GawgguFyGrWKav7AX4VKUg"
}

Импорт:

const key = await crypto.subtle.importKey(
  "jwk",
  jwkObject,
  {
    name: "RSA-PSS",
    hash: "SHA-256"
  },
  false,
  ["sign"]
);

Особенности формата:

  • полностью текстовый JSON
  • удобен для передачи через API
  • легко сериализуется и хранится
  • требует Base64URL кодирования полей
  • может включать метаданные (kid, use, alg)

Ключевые поля:

  • kty — тип ключа (RSA, EC, oct)
  • use — назначение (sig, enc)
  • alg — алгоритм
  • ext — разрешение на экспорт
  • kid — идентификатор ключа

В отличие от PKCS#8 и SPKI, JWK не требует бинарного парсинга ASN.1, что делает его удобным в JavaScript-экосистемах.


Сопоставление форматов с типами ключей

Разные форматы связаны с разными классами криптографических ключей:

  • raw → симметричные ключи (AES, HMAC)
  • pkcs8 → приватные ключи RSA/ECC
  • spki → публичные ключи RSA/ECC
  • jwk → универсальное JSON-представление

Асимметричная криптография всегда использует пару форматов:

  • приватный ключ → PKCS#8 или JWK
  • публичный ключ → SPKI или JWK

Внутреннее представление и преобразования

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

При импорте происходит несколько стадий:

  1. Декодирование (Base64, DER, JSON)
  2. Парсинг структуры (ASN.1 или JSON)
  3. Валидация алгоритма
  4. Приведение к внутреннему объекту CryptoKey

Форматы PKCS#8 и SPKI требуют ASN.1 DER парсинга, что делает их более «низкоуровневыми» по сравнению с JWK.


Кодирование и преобразования PEM

PKCS#8 и SPKI часто встречаются в PEM-обёртке. Перед импортом выполняется преобразование:

  • удаление заголовков -----BEGIN ...-----
  • удаление переносов строк
  • Base64-декодирование
  • получение ArrayBuffer

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

function pemToArrayBuffer(pem) {
  const base64 = pem
    .replace(/-----[^-]+-----/g, "")
    .replace(/\s/g, "");

  const binary = atob(base64);
  const buffer = new ArrayBuffer(binary.length);
  const view = new Uint8Array(buffer);

  for (let i = 0; i < binary.length; i++) {
    view[i] = binary.charCodeAt(i);
  }

  return buffer;
}

Ограничения и совместимость

Форматы имеют строгие ограничения:

  • raw не используется для RSA/ECC
  • pkcs8 не содержит публичной части ключа
  • spki не содержит приватной информации
  • jwk требует корректного Base64URL кодирования

Несовместимость формата и алгоритма приводит к ошибке DataError.


Практические различия моделей хранения

PKCS#8 и SPKI ориентированы на инфраструктурную совместимость:

  • TLS сертификаты
  • OpenSSL
  • аппаратные модули

JWK ориентирован на веб-приложения:

  • OAuth 2.0
  • OpenID Connect
  • JWT подписи

RAW ориентирован на симметричную криптографию внутри приложения:

  • шифрование локальных данных
  • защищённые сессии

Типовые ошибки при импорте

При работе с форматами импорта возникают повторяющиеся проблемы:

  • несоответствие алгоритма ключу
  • использование PKCS#8 вместо SPKI
  • передача PEM без декодирования
  • использование Base64 вместо Base64URL в JWK
  • неправильная длина RAW ключа

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


Роль форматов в архитектуре WebCrypto

Разделение на raw, pkcs8, spki, jwk отражает несколько уровней абстракции:

  • RAW — минимальный уровень криптографического материала
  • PKCS#8 / SPKI — бинарные стандарты обмена ключами
  • JWK — JSON-уровень веб-интероперабельности

Эта модель обеспечивает совместимость между:

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