Взаимодействие jsrsasign и Web Crypto API

Библиотека jsrsasign реализует криптографические операции в JavaScript на уровне пользовательского пространства выполнения, включая RSA, ECDSA, X.509, CMS и работу с ASN.1 структурами. В отличие от неё Web Crypto API является встроенным системным интерфейсом браузера, предоставляющим доступ к нативной криптографии через объект crypto.subtle.

Ключевое различие заключается в модели представления ключей:

  • jsrsasign использует текстовые форматы PEM/DER и собственные структуры объектов
  • Web Crypto API оперирует объектами CryptoKey, которые являются непрозрачными (non-exportable в некоторых режимах)

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


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

jsrsasign активно работает с PEM-форматом:

-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

Web Crypto API не принимает PEM напрямую. Для работы требуется импорт в формате ArrayBuffer и структурированное описание алгоритма:

crypto.subtle.importKey(
  "pkcs8",
  arrayBufferKey,
  algorithmParameters,
  true,
  ["sign"]
)

Таким образом возникает обязательный слой конвертации:

  • PEM → Base64 decode → ArrayBuffer → CryptoKey
  • CryptoKey → exportKey → ArrayBuffer → PEM (при необходимости)

jsrsasign содержит утилиты для парсинга ASN.1, что позволяет использовать его как промежуточный трансформатор.


Преобразование RSA ключей между jsrsasign и Web Crypto API

RSA ключи в jsrsasign часто представлены через объект RSAKey:

const rsa = KEYUTIL.getKey(pemPrivateKey);

Для Web Crypto API требуется формат PKCS#8 или SPKI.

Из PEM в Web Crypto API

Процесс включает несколько этапов:

  1. Извлечение DER из PEM
  2. Конвертация в ArrayBuffer
  3. Импорт через subtle.importKey

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

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;
}

После этого ключ может быть импортирован:

const key = await crypto.subtle.importKey(
  "pkcs8",
  pemToArrayBuffer(privateKeyPem),
  {
    name: "RSASSA-PKCS1-v1_5",
    hash: "SHA-256"
  },
  true,
  ["sign"]
);

Использование jsrsasign для генерации ключей с последующим импортом в Web Crypto API

jsrsasign может генерировать RSA ключи:

const kp = KEYUTIL.generateKeypair("RSA", 2048);
const privateKeyPem = KEYUTIL.getPEM(kp.prvKeyObj, "PKCS8PRV");
const publicKeyPem = KEYUTIL.getPEM(kp.pubKeyObj);

Далее эти ключи преобразуются для Web Crypto API.

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


Подпись данных: jsrsasign vs Web Crypto API

jsrsasign:

const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(privateKeyPem);
sig.updateString("data");
const signature = sig.sign();

Web Crypto API:

const signature = await crypto.subtle.sign(
  {
    name: "RSASSA-PKCS1-v1_5"
  },
  cryptoKey,
  new TextEncoder().encode("data")
);

Основное различие:

  • jsrsasign возвращает строку (hex/base64)
  • Web Crypto API возвращает ArrayBuffer

Это требует дополнительного преобразования при совместном использовании.


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

Часто возникает сценарий:

  • подпись создаётся в jsrsasign
  • проверяется в Web Crypto API

Проблема заключается в несовпадении кодировок.

Пример несовместимости

jsrsasign:

const sigHex = signature; // hex string

Web Crypto API ожидает:

ArrayBuffer

Решение — декодирование hex:

function hexToBuffer(hex) {
  const buffer = new ArrayBuffer(hex.length / 2);
  const view = new Uint8Array(buffer);

  for (let i = 0; i < hex.length; i += 2) {
    view[i / 2] = parseInt(hex.substr(i, 2), 16);
  }

  return buffer;
}

ECDSA: различия представления ключей и подписей

jsrsasign использует:

KJUR.crypto.ECDSA

Web Crypto API использует:

{
  name: "ECDSA",
  namedCurve: "P-256"
}

Ключевая проблема — формат подписи:

  • jsrsasign: DER-encoded signature
  • Web Crypto API: raw concatenated r|s или DER в зависимости от реализации

Конвертация ECDSA подписей

При переходе между системами часто требуется декодирование ASN.1 структуры:

jsrsasign предоставляет:

ASN1HEX.parse

Но Web Crypto API ожидает фиксированную бинарную структуру.


Работа с X.509 сертификатами

jsrsasign имеет полноценный парсер сертификатов:

const cert = new X509();
cert.readCertPEM(pemCert);

Web Crypto API не предоставляет встроенного парсинга X.509, но позволяет импортировать публичный ключ:

crypto.subtle.importKey(
  "spki",
  spkiBuffer,
  { name: "RSA-PSS", hash: "SHA-256" },
  true,
  ["verify"]
);

Таким образом jsrsasign используется как слой извлечения публичного ключа из сертификата.


Извлечение SPKI из сертификата jsrsasign

const x = new X509();
x.readCertPEM(certPem);

const publicKey = KEYUTIL.getKey(x.getPublicKey());
const spkiPem = KEYUTIL.getPEM(publicKey);

Далее выполняется конвертация PEM → ArrayBuffer.


Совместное использование в гибридной архитектуре

Типичная схема взаимодействия:

  • jsrsasign: генерация ключей, работа с PEM, ASN.1, сертификаты
  • Web Crypto API: подпись, шифрование, аппаратное ускорение

Поток данных:

  1. Генерация ключа в jsrsasign
  2. Экспорт в PEM
  3. Импорт в Web Crypto API
  4. Выполнение операций через subtle
  5. При необходимости экспорт обратно в PEM

Производительность и ограничения

jsrsasign:

  • полностью JavaScript-реализация
  • предсказуемое поведение
  • медленнее при больших объёмах операций

Web Crypto API:

  • использует нативные реализации (OpenSSL / BoringSSL / SChannel)
  • значительно быстрее
  • ограничен браузерной моделью безопасности
  • невозможность прямого доступа к сырому материалу ключа

Типичные ошибки при интеграции

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

RSA-PKCS1 vs RSA-PSS приводит к невозможности проверки подписи.

Неверный формат ключа

PKCS#1 вместо PKCS#8 вызывает ошибку DataError.

Проблемы кодировки

Base64URL vs Base64 приводит к некорректной загрузке ключей.


Практика выбора между библиотеками

Использование jsrsasign оправдано при:

  • необходимости работы с сертификатами X.509
  • парсинге ASN.1 структур
  • генерации PEM на клиенте
  • совместимости с legacy системами

Web Crypto API предпочтителен при:

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

Гибридная модель криптографического слоя

На практике обе технологии часто используются совместно в одной архитектуре:

  • jsrsasign как слой интероперабельности форматов
  • Web Crypto API как слой исполнения криптографических операций

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