Экспорт и импорт RSA-ключей в форматах spki и pkcs8

В Web Crypto API экспорт и импорт ключей основаны на стандартизированных форматах представления криптографических ключей. Для RSA-ключей используются два основных контейнера: SPKI (SubjectPublicKeyInfo) для публичных ключей и PKCS#8 (PrivateKeyInfo) для приватных ключей. Эти форматы определяют структуру бинарного представления ключей в соответствии с ASN.1 и широко применяются в криптографических системах за пределами браузера.

SPKI содержит только публичную часть ключа вместе с алгоритмическими параметрами. PKCS#8, напротив, инкапсулирует приватный ключ и дополнительные метаданные, включая идентификатор алгоритма.


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

Перед экспортом и импортом необходимо создать ключевую пару с помощью crypto.subtle.generateKey. Важно указать, что ключи должны быть извлекаемыми (extractable: true), иначе экспорт будет невозможен.

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

Параметр true во втором аргументе означает разрешение на экспорт ключей. Без него операции exportKey завершатся ошибкой безопасности.


Экспорт публичного ключа в SPKI

Формат SPKI используется для передачи публичного ключа между системами. В Web Crypto API экспорт выполняется через crypto.subtle.exportKey.

const spkiBuffer = await crypto.subtle.exportKey(
  "spki",
  keyPair.publicKey
);

Результатом является ArrayBuffer, содержащий бинарное представление ключа. Этот формат не предназначен для чтения человеком и обычно кодируется в Base64 при сохранении или передаче.


Преобразование SPKI в Base64

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

function arrayBufferToBase64(buffer) {
  const bytes = new Uint8Array(buffer);
  let binary = "";
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

const spkiBase64 = arrayBufferToBase64(spkiBuffer);

Base64-представление часто используется в JSON API, HTTP-заголовках и конфигурационных файлах.


Импорт публичного ключа из SPKI

Импорт осуществляется через crypto.subtle.importKey. Необходимо точно указать алгоритм и параметры, совпадающие с теми, что использовались при генерации.

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

Важно соблюдать соответствие алгоритма: ключ RSA-OAEP нельзя импортировать как RSASSA-PKCS1-v1_5 и наоборот.


Экспорт приватного ключа в PKCS#8

PKCS#8 представляет приватный ключ в стандартизированном формате. Экспорт выполняется аналогично публичному ключу:

const pkcs8Buffer = await crypto.subtle.exportKey(
  "pkcs8",
  keyPair.privateKey
);

Полученный ArrayBuffer содержит не только сам ключ, но и структуру ASN.1 с идентификатором алгоритма и параметрами.


Преобразование PKCS#8 в Base64

Приватные ключи часто сохраняются в зашифрованных хранилищах или конфигурационных файлах:

const pkcs8Base64 = arrayBufferToBase64(pkcs8Buffer);

В реальных системах рекомендуется дополнительно шифровать такие данные перед сохранением.


Импорт приватного ключа из PKCS#8

Импорт приватного ключа требует указания алгоритма и разрешённых операций.

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

Если ключ предназначен для подписи (например, RSASSA-PKCS1-v1_5), набор операций будет другим:

["sign"]

Совместимость форматов и алгоритмов

SPKI и PKCS#8 не зависят от конкретной реализации браузера, но зависят от криптографического алгоритма. Основные правила:

  • SPKI используется только для публичных ключей
  • PKCS#8 используется только для приватных ключей
  • Алгоритм при импорте должен совпадать с алгоритмом при генерации
  • Хэш-функция (SHA-256, SHA-384 и т.д.) должна быть идентичной

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

Несовпадение алгоритма

Попытка импортировать ключ RSA-OAEP как RSASSA-PKCS1-v1_5 приводит к ошибке DataError.


extractable: false

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


Некорректный формат данных

Передача повреждённого Base64 или обрезанного ArrayBuffer приводит к ошибке декодирования ASN.1 структуры.


Работа с бинарными данными Web Crypto

Web Crypto API всегда работает с бинарными буферами (ArrayBuffer), а не строками. Это требует явного преобразования при интеграции с внешними системами.

Для декодирования Base64 обратно в ArrayBuffer:

function base64ToArrayBuffer(base64) {
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);

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

  return bytes.buffer;
}

Практическая модель обмена ключами

Типичный сценарий использования SPKI и PKCS#8 в приложениях:

  1. Генерация ключевой пары в браузере
  2. Экспорт публичного ключа в SPKI
  3. Передача публичного ключа серверу
  4. Шифрование данных на сервере с использованием публичного ключа
  5. Дешифрование в браузере приватным ключом из PKCS#8 (или наоборот)

Особенности безопасности

RSA-ключи в Web Crypto API защищены изоляцией контекста браузера. Однако при экспорте в SPKI или PKCS#8 ключи становятся уязвимыми к компрометации, если хранятся в открытом виде.

Критические моменты:

  • приватный ключ PKCS#8 никогда не должен передаваться по сети без шифрования
  • SPKI может быть публично доступен, но требует контроля целостности
  • extractable ключи увеличивают риск утечки при XSS-уязвимостях

Использование RSA-OAEP и RSASSA-PKCS1 в контексте SPKI/PKCS#8

Форматы SPKI и PKCS#8 не зависят от схемы использования RSA, однако поведение ключей определяется при импорте:

  • RSA-OAEP — используется для шифрования
  • RSASSA-PKCS1-v1_5 — используется для цифровой подписи

Пример различий:

// шифрование
["encrypt", "decrypt"]

// подпись
["sign", "verify"]

Хранение ключей в браузере

После импорта ключи могут храниться в памяти или сериализоваться через IndexedDB. Однако повторная сериализация невозможна без повторного экспорта, так как Web Crypto API не предоставляет прямого доступа к структуре ключа.


Итоговая модель представления ключей

  • SPKI — контейнер публичного ключа RSA
  • PKCS#8 — контейнер приватного ключа RSA
  • Web Crypto API оперирует бинарными представлениями
  • экспорт и импорт требуют строгого соответствия алгоритмов
  • Base64 используется только как транспортный слой над бинарными данными