Конвертация PEM → JWK

PEM (Privacy-Enhanced Mail) представляет собой текстовый контейнер для криптографических ключей и сертификатов, закодированных в Base64 и обёрнутых в специальные маркеры вида:

-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

или

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

В экосистеме JavaScript работа с PEM часто связана с библиотекой Jsrsasign, которая предоставляет инструменты для парсинга ASN.1 структур, извлечения криптографических параметров и преобразования их в современные форматы, включая JWK (JSON Web Key).


PEM является транспортным представлением ключа, тогда как JWK — это JSON-структура, предназначенная для веб-экосистемы и спецификации JOSE (JSON Object Signing and Encryption).

JWK для RSA-ключа может выглядеть так:

  • kty — тип ключа (RSA, EC)
  • n — модуль (modulus)
  • e — экспонента
  • d — приватная экспонента (для приватных ключей)
  • p, q, dp, dq, qi — CRT параметры

Пример структуры:

{
  "kty": "RSA",
  "n": "...",
  "e": "...",
  "d": "...",
  "p": "...",
  "q": "...",
  "dp": "...",
  "dq": "...",
  "qi": "..."
}

PEM содержит ASN.1 DER-структуру, из которой эти значения извлекаются.


Основной механизм Jsrsasign при работе с PEM

В Jsrsasign ключевая точка входа для преобразований — объект KEYUTIL.

Он выполняет следующие операции:

  • декодирование Base64 PEM
  • парсинг ASN.1 структуры
  • извлечение параметров RSA/EC ключа
  • формирование объектов KeyObject
  • конвертация в JWK

Основная функция загрузки PEM:

const key = KEYUTIL.getKey(pem);

На этом этапе происходит определение типа ключа:

  • RSAKey
  • KJUR.crypto.ECDSA
  • приватный или публичный ключ

Конвертация PEM → JWK в Jsrsasign

После получения объекта ключа выполняется преобразование:

const jwk = KEYUTIL.getJWK(key);

Этот метод анализирует объект ключа и формирует JSON Web Key согласно спецификации RFC 7517.


Пример полного преобразования RSA ключа

Исходный PEM

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr...
-----END PUBLIC KEY-----

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

import { KEYUTIL } from "jsrsasign";

const pem = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr...
-----END PUBLIC KEY-----`;

const key = KEYUTIL.getKey(pem);
const jwk = KEYUTIL.getJWK(key);

console.log(jwk);

Внутренние этапы преобразования

1. Декодирование PEM

PEM очищается от заголовков и переводится из Base64 в бинарный DER-формат.

2. ASN.1 парсинг

Jsrsasign использует ASN.1 парсер для извлечения структуры:

  • SEQUENCE
  • INTEGER (modulus)
  • INTEGER (exponent)

3. Формирование RSAKey объекта

Из ASN.1 создаётся объект:

  • n (BigInteger)
  • e (BigInteger)
  • d (если приватный ключ)
  • CRT параметры

4. Конвертация в Base64URL

JWK требует кодирование в base64url, а не стандартный Base64:

  • удаляются + и /
  • убираются =
  • применяется URL-safe формат

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

При обработке PEM приватного RSA ключа структура JWK расширяется:

const key = KEYUTIL.getKey(privatePem);
const jwk = KEYUTIL.getJWK(key);

Дополнительно появляются поля:

  • d — приватная экспонента
  • p, q — простые множители
  • dp, dq — CRT оптимизации
  • qi — коэффициент обратного модуля

Эти параметры позволяют ускорить операции подписи и расшифровки.


Конвертация EC ключей

Для эллиптических кривых структура отличается:

  • crv — кривая (P-256, P-384 и т.д.)
  • x, y — координаты точки
  • d — приватный параметр

Пример:

const jwk = KEYUTIL.getJWK(KEYUTIL.getKey(ecPem));

Роль алгоритма определения типа ключа

Jsrsasign автоматически определяет тип PEM:

  • PUBLIC KEY → SubjectPublicKeyInfo
  • PRIVATE KEY → PKCS#8
  • RSA PRIVATE KEY → PKCS#1
  • EC PRIVATE KEY → SEC1

Это важно, потому что от формата зависит способ извлечения параметров.


Преобразование через промежуточный объект KEY

Иногда используется явное разделение этапов:

const keyObj = KEYUTIL.getKey(pem);

Далее возможны операции:

  • экспорт в PEM
  • преобразование в JWK
  • получение thumbprint
  • извлечение сертификатных данных

Контроль корректности JWK

После конвертации важно учитывать:

  • обязательное наличие kty
  • корректность base64url полей
  • соответствие длины ключа (RSA 2048 / 3072 / 4096)
  • валидность EC кривой

Jsrsasign не всегда выполняет строгую валидацию RFC, поэтому результат может требовать дополнительной проверки.


Частые ошибки при конвертации PEM → JWK

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

Отсутствие заголовков приводит к ошибке парсинга ASN.1.

Неподдерживаемый тип ключа

Некоторые нестандартные форматы PKCS могут не распознаваться.

Повреждённая Base64 строка

Даже один лишний символ делает DER-структуру нечитаемой.


Использование JWK в связке с Jsrsasign

После конвертации JWK может использоваться для:

  • JWT подписи (HS256 / RS256 / ES256)
  • проверки токенов
  • интеграции с OAuth2 / OpenID Connect

Пример использования RSA JWK:

const header = { alg: "RS256", typ: "JWT" };

const sHeader = JSON.stringify(header);
const sPayload = JSON.stringify({ sub: "1234567890" });

const key = KEYUTIL.getKey(pem);
const jwk = KEYUTIL.getJWK(key);

const jwt = KJUR.jws.JWS.sign(null, sHeader, sPayload, jwk);

Важные особенности base64url в JWK

Jsrsasign автоматически преобразует значения:

  • BigInteger → byte array
  • byte array → Base64
  • Base64 → Base64URL

Это критично, так как стандарт JWK требует URL-safe представление без padding.


Взаимная конвертация форматов

Jsrsasign поддерживает двунаправленные преобразования:

  • PEM → KEY → JWK
  • JWK → KEY → PEM

Обратная операция:

const key = KEYUTIL.getKey(jwk);
const pem = KEYUTIL.getPEM(key);

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

Если PEM содержит сертификат:

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

Jsrsasign сначала извлекает публичный ключ из X.509 структуры, затем преобразует его в JWK.


ASN.1 как основа преобразования

Ключевая роль ASN.1 в процессе:

  • определение структуры ключа
  • извлечение INTEGER компонентов
  • интерпретация PKCS стандартов

Без корректного ASN.1 парсинга преобразование PEM → JWK невозможно.


Оптимизация обработки ключей

При массовой конвертации рекомендуется:

  • кэшировать результат KEYUTIL.getKey
  • избегать повторного парсинга PEM
  • разделять публичные и приватные ключи

Совместимость с Web Crypto API

JWK, полученный через Jsrsasign, может использоваться как промежуточный формат для:

  • crypto.subtle.importKey
  • crypto.subtle.sign
  • crypto.subtle.verify

Однако может потребоваться адаптация структуры под строгий Web Crypto API формат.


Типовая архитектура преобразования

  1. Получение PEM (файл / API / сертификат)
  2. Парсинг через KEYUTIL.getKey
  3. Извлечение параметров ASN.1
  4. Формирование JWK через KEYUTIL.getJWK
  5. Использование в JWT / криптографических операциях