Создание зашифрованного токена: CompactEncrypt

Шифрование в формате JWE Compact Serialization в библиотеке jose строится вокруг класса CompactEncrypt, который реализует создание зашифрованного JSON Web Encryption токена в компактной строковой форме. Этот формат используется, когда требуется не подпись, а именно конфиденциальность данных: полезная нагрузка полностью шифруется и становится недоступной без ключа расшифровки.

Compact JWE состоит из пяти частей, разделённых точками:

header.encryptedKey.iv.ciphertext.tag

Каждая часть кодируется в Base64URL. В отличие от JWS, здесь содержимое не читается, а полностью зашифровано.

В библиотеке jose процесс формирования этого токена инкапсулирован в CompactEncrypt, который последовательно:

  • принимает полезную нагрузку (payload)
  • формирует защищённый заголовок (protected header)
  • применяет алгоритмы шифрования
  • генерирует IV, ciphertext и authentication tag
  • возвращает готовую компактную строку

Базовый процесс создания JWE токена

Ключевым элементом является симметричный или асимметричный ключ в формате JWK или CryptoKey.

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

import { CompactEncrypt, generateSecret } from 'jose'

// создание ключа шифрования
const secretKey = await generateSecret('A256GCM')

// полезная нагрузка
const payload = new TextEncoder().encode(
  JSON.stringify({ userId: 123, role: 'admin' })
)

// создание JWE
const jwe = await new CompactEncrypt(payload)
  .setProtectedHeader({
    alg: 'dir',
    enc: 'A256GCM'
  })
  .encrypt(secretKey)

console.log(jwe)

Структура Protected Header

Protected header — это JSON-объект, который защищён шифрованием и участвует в расчёте целостности токена.

Основные параметры:

  • alg — алгоритм управления ключом
  • enc — алгоритм шифрования содержимого

Пример:

{
  "alg": "dir",
  "enc": "A256GCM"
}

Алгоритм dir означает прямое использование симметричного ключа без его обёртки (Key Wrapping). Это самый простой сценарий, подходящий для shared secret.

Использование различных алгоритмов

Прямое шифрование (dir)

Используется, когда один и тот же ключ применяется для шифрования и расшифровки.

.setProtectedHeader({
  alg: 'dir',
  enc: 'A256GCM'
})

RSA-OAEP + AES

При асимметричном подходе используется публичный ключ для защиты симметричного CEK (Content Encryption Key).

.setProtectedHeader({
  alg: 'RSA-OAEP-256',
  enc: 'A256GCM'
})

В этом случае библиотека автоматически:

  • генерирует CEK
  • шифрует CEK публичным RSA ключом
  • использует CEK для шифрования payload

Процесс внутри CompactEncrypt

При вызове .encrypt(key) происходит несколько этапов:

  1. Формирование защищённого заголовка
  2. Генерация Content Encryption Key (если требуется)
  3. Выбор IV (initialization vector)
  4. Шифрование payload алгоритмом enc
  5. Вычисление authentication tag
  6. Сериализация всех частей в compact format

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

import { CompactEncrypt, importJWK } from 'jose'

const publicKeyJwk = {
  kty: 'RSA',
  e: 'AQAB',
  n: '...'
}

const publicKey = await importJWK(publicKeyJwk, 'RSA-OAEP-256')

const payload = new TextEncoder().encode('sensitive data')

const jwe = await new CompactEncrypt(payload)
  .setProtectedHeader({
    alg: 'RSA-OAEP-256',
    enc: 'A256GCM'
  })
  .encrypt(publicKey)

Важные особенности сериализации

Compact формат имеет ограничения:

  • может содержать только один recipient
  • не поддерживает мультиадресное шифрование
  • не допускает незашифрованный payload
  • всегда использует Base64URL без padding

Это делает его компактным и удобным для HTTP заголовков, cookies или URL-safe передачи.

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

Payload в jose всегда передаётся как Uint8Array. Это означает, что строковые данные должны быть предварительно закодированы:

const encoder = new TextEncoder()

const payload = encoder.encode(
  JSON.stringify({
    session: 'abc123',
    expires: 1710000000
  })
)

При расшифровке используется TextDecoder.

Управление алгоритмами шифрования

Поддерживаемые enc алгоритмы:

  • A128GCM
  • A192GCM
  • A256GCM

На практике чаще всего используется A256GCM как наиболее устойчивый вариант.

Безопасность ключей

При использовании dir критически важно:

  • не хранить ключ в клиентском коде
  • использовать secure storage (env, vault)
  • регулярно ротировать ключи

При RSA сценарии:

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

Типичные ошибки при использовании CompactEncrypt

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

Если ключ не соответствует алгоритму, библиотека выбросит ошибку во время .encrypt().

Несоответствие alg и ключа

Например, использование RSA-OAEP-256 с симметричным ключом невозможно.

Попытка использовать несколько recipients

CompactEncrypt этого не поддерживает, в таком случае требуется General JWE JSON Serialization.

Разбор результата Compact JWE

Полученная строка выглядит примерно так:

eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..vZ3...8kQ..m9P...Q8

Разделы:

  • header — JSON с alg/enc
  • encrypted key — пустой при dir
  • IV — случайный вектор
  • ciphertext — зашифрованные данные
  • tag — аутентификационный тег

Использование в реальных сценариях

CompactEncrypt чаще всего применяется для:

  • защищённых cookies (session tokens)
  • API-токенов с конфиденциальной нагрузкой
  • передачи чувствительных данных через URL
  • мобильных клиентов, где важен компактный размер

Особенно полезен в архитектурах, где требуется:

  • отсутствие серверного хранения состояния
  • шифрование payload вместо подписывания
  • минимальный overhead передачи

Декодирование и проверка целостности

Хотя основная операция — шифрование, важный аспект заключается в том, что GCM режим обеспечивает:

  • конфиденциальность данных
  • целостность (integrity)
  • защиту от подмены ciphertext

Любое изменение строки приводит к ошибке расшифровки.

Производительность CompactEncrypt

Факторы влияния:

  • размер payload (линейная зависимость)
  • алгоритм (A256GCM тяжелее A128GCM)
  • асимметричное шифрование (RSA дороже по CPU)

Для high-load систем предпочтительно:

  • использовать dir с заранее распределёнными ключами
  • избегать RSA для каждого запроса при массовых операциях