Создание Flattened JWE: FlattenedEncrypt

Flattened JWE представляет собой компактное JSON-представление зашифрованного сообщения, в котором результат шифрования хранится в виде единого объекта с явным разделением полей. В отличие от General JWE Serialization, flattened-формат не использует массив recipients и предназначен для сценариев с одним получателем.

Структура включает ключевые компоненты:

  • protected header — защищённый заголовок, сериализованный и закодированный в base64url
  • unprotected header — необязательные параметры, не защищённые целостностью
  • encrypted key — зашифрованный симметричный ключ (CEK)
  • initialization vector — вектор инициализации
  • ciphertext — зашифрованные данные
  • authentication tag — тег аутентификации

Flattened формат применяется, когда требуется минимизировать структуру без потери криптографических гарантий.


Архитектура FlattenedEncrypt в jose

В библиотеке jose реализация Flattened JWE представлена классом FlattenedEncrypt. Он инкапсулирует процесс формирования JWE, начиная от установки заголовков и заканчивая финальной сериализацией.

Основная идея заключается в поэтапном построении объекта:

  1. установка защищённого заголовка
  2. добавление дополнительных параметров
  3. настройка алгоритма шифрования ключа
  4. указание получателя (ключа шифрования)
  5. выполнение операции шифрования

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

Типичный сценарий начинается с создания экземпляра FlattenedEncrypt, которому передаётся исходный plaintext.

import { FlattenedEncrypt } from 'jose'

Создание объекта шифрования

const encoder = new TextEncoder()

const jwe = await new FlattenedEncrypt(
  encoder.encode('секретные данные')
)

На этом этапе данные ещё не зашифрованы, объект лишь подготавливает структуру.


Формирование защищённого заголовка

Protected header определяет алгоритмы и параметры шифрования. Он является критически важной частью JWE, так как участвует в вычислении authentication tag.

const jwe = await new FlattenedEncrypt(
  encoder.encode('секретные данные')
)
  .setProtectedHeader({
    alg: 'RSA-OAEP-256',
    enc: 'A256GCM'
  })

Роль параметров

  • alg — алгоритм шифрования ключа (Key Management Algorithm)
  • enc — алгоритм симметричного шифрования содержимого

Комбинация этих параметров определяет криптографическую стойкость всей конструкции.


Использование ключа получателя

FlattenedEncrypt требует публичный ключ получателя для шифрования CEK (Content Encryption Key). В случае RSA используется криптографический ключ формата KeyLike.

import { importJWK } from 'jose'

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

Далее ключ подключается через метод encryptKey:

const jwe = await new FlattenedEncrypt(
  encoder.encode('секретные данные')
)
  .setProtectedHeader({
    alg: 'RSA-OAEP-256',
    enc: 'A256GCM'
  })
  .encryptKey(publicKey)

Завершение процесса шифрования

Финальная стадия выполняется методом encrypt(), который принимает дополнительные параметры, включая IV и AAD (если требуется).

const result = await new FlattenedEncrypt(
  encoder.encode('секретные данные')
)
  .setProtectedHeader({
    alg: 'RSA-OAEP-256',
    enc: 'A256GCM'
  })
  .encryptKey(publicKey)
  .encrypt()

Результатом является объект Flattened JWE:

{
  "protected": "base64url(header)",
  "encrypted_key": "base64url(...)", 
  "iv": "base64url(...)",
  "ciphertext": "base64url(...)",
  "tag": "base64url(...)"
}

Расширенная настройка: AAD и дополнительные заголовки

Дополнительные данные аутентификации (Additional Authenticated Data) позволяют привязать внешнюю информацию к криптограмме без её шифрования.

const jwe = await new FlattenedEncrypt(
  encoder.encode('секретные данные')
)
  .setProtectedHeader({
    alg: 'RSA-OAEP-256',
    enc: 'A256GCM',
    typ: 'JWE'
  })
  .setAdditionalAuthenticatedData(
    encoder.encode('контекст сообщения')
  )
  .encryptKey(publicKey)
  .encrypt()

AAD участвует в вычислении тега аутентификации, но не входит в ciphertext.


Использование unprotected header

Flattened JWE позволяет разделять защищённые и незащищённые метаданные. Unprotected header не включается в вычисление подписи.

const jwe = await new FlattenedEncrypt(
  encoder.encode('секретные данные')
)
  .setProtectedHeader({
    alg: 'RSA-OAEP-256',
    enc: 'A256GCM'
  })
  .setUnprotectedHeader({
    kid: 'key-1'
  })
  .encryptKey(publicKey)
  .encrypt()

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


Взаимодействие алгоритмов alg и enc

Разделение ответственности между alg и enc является ключевым принципом JWE:

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

Пример типичных комбинаций:

  • RSA-OAEP-256 + A256GCM
  • ECDH-ES + A256GCM
  • A256KW + A128CBC-HS256

Каждая комбинация определяет как безопасность, так и производительность операции.


Особенности Flattened формата в jose

Реализация FlattenedEncrypt в jose ориентирована на строгую совместимость со стандартом JWE RFC 7516. Основные особенности:

  • отсутствие массива recipients
  • фиксированная структура результата
  • поддержка всех современных алгоритмов jose
  • возможность тонкой настройки заголовков
  • поддержка streaming-ориентированного подхода через Uint8Array

Flattened формат чаще используется в API-сценариях, где каждый JWE связан с одним получателем и требуется минимальный размер JSON-структуры.


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

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

Если алгоритм не соответствует типу ключа, шифрование завершится ошибкой на этапе encryptKey.

Отсутствие enc

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

Использование неподходящего ключа

RSA-алгоритмы требуют публичного ключа, ECIES — соответствующей пары ключей.


Потоковая модель данных

FlattenedEncrypt работает с бинарными данными через Uint8Array. Это позволяет избежать лишних преобразований строк:

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

Результаты также возвращаются в виде base64url-строк, что обеспечивает совместимость с JSON-сериализацией.


Структурная модель процесса шифрования

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

  1. plaintext → Uint8Array
  2. генерация CEK
  3. шифрование CEK публичным ключом
  4. шифрование payload симметричным алгоритмом
  5. формирование authentication tag
  6. сборка JSON Flattened JWE

Поведение при сериализации результата

После выполнения encrypt() библиотека возвращает обычный JavaScript-объект. Он может быть напрямую сериализован:

JSON.stringify(jwe)

При этом порядок полей не влияет на криптографическую корректность, так как проверка осуществляется через protected header и authentication tag.