Расшифровка Flattened JWE: flattenedDecrypt

flattenedDecrypt используется для расшифровки JWE (JSON Web Encryption) в так называемом flattened-формате, который представляет собой компактную структуру с одним получателем и минимальной вложенностью. В библиотеке jose этот метод относится к низкоуровневым операциям работы с зашифрованными JWT и позволяет вручную управлять процессом дешифрования.

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

Типичная структура выглядит следующим образом:

  • protected — base64url-закодированный заголовок
  • unprotected — необязательный незашифрованный заголовок
  • header — объединённый заголовок (редко используется одновременно с protected/unprotected)
  • encrypted_key — зашифрованный CEK (Content Encryption Key)
  • iv — вектор инициализации
  • ciphertext — зашифрованные данные
  • tag — аутентификационный тег

Именно эту структуру ожидает flattenedDecrypt.

Общая логика работы flattenedDecrypt

Процесс расшифровки можно разложить на несколько этапов:

  1. Извлечение параметров JWE-объекта
  2. Декодирование protected header
  3. Определение алгоритмов (alg, enc)
  4. Расшифровка CEK (если используется асимметричное шифрование)
  5. Расшифровка ciphertext с использованием CEK и IV
  6. Проверка authentication tag
  7. Возврат исходного payload

В библиотеке jose эти шаги инкапсулированы внутри flattenedDecrypt.

Сигнатура flattenedDecrypt

Метод имеет следующую структуру вызова:

import { flattenedDecrypt } from 'jose'

const { plaintext, protectedHeader, additionalAuthenticatedData } =
  await flattenedDecrypt(jwe, key)

Параметры:

  • jwe — объект в flattened-формате
  • key — ключ расшифровки (JWK, CryptoKey или секрет)

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

Рассмотрим базовый сценарий расшифровки симметрично зашифрованного JWE:

import { flattenedDecrypt } from 'jose'

const jwe = {
  protected: 'eyJlbmMiOiJBMjU2R0NNIn0',
  iv: '48V1_ALb6US04U3b',
  ciphertext: '5eym8TW_c8Su...',
  tag: 'XFBoMYUZodetZdvTiFvSkQ',
}

const key = new TextEncoder().encode('supersecretsharedkey123')

const { plaintext } = await flattenedDecrypt(jwe, key)

console.log(new TextDecoder().decode(plaintext))

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

Работа с protectedHeader

После декодирования protected заголовка он становится доступен в виде объекта:

const { protectedHeader } = await flattenedDecrypt(jwe, key)

console.log(protectedHeader.alg)
console.log(protectedHeader.enc)

Ключевые поля:

  • alg — алгоритм шифрования ключа (например, RSA-OAEP, ECDH-ES)
  • enc — алгоритм шифрования содержимого (например, A256GCM)

Эти значения критически важны для корректной интерпретации структуры JWE.

Ассиметричное шифрование в flattenedDecrypt

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

import { flattenedDecrypt } from 'jose'
import { importPKCS8 } from 'jose/key/import'

const privateKey = await importPKCS8(pem, 'RSA-OAEP-256')

const { plaintext } = await flattenedDecrypt(jwe, privateKey)

В этом случае процесс включает дополнительный этап:

  • расшифровка encrypted_key с использованием приватного ключа
  • получение Content Encryption Key (CEK)
  • дальнейшая симметричная расшифровка ciphertext

Обработка additionalAuthenticatedData

Некоторые JWE могут содержать дополнительную аутентифицированную информацию (AAD), которая участвует в проверке целостности:

const { additionalAuthenticatedData } = await flattenedDecrypt(jwe, key)

AAD не расшифровывается, но влияет на проверку authentication tag. Если данные были изменены, проверка целостности завершится ошибкой.

Ошибки при flattenedDecrypt

На практике чаще всего встречаются следующие ошибки:

1. Ошибка ключа

Если ключ не соответствует алгоритму alg:

  • invalid key size
  • decryption failed

Причина: неверный тип ключа или неправильный формат JWK.

2. Ошибка authentication tag

Возникает при изменении ciphertext или tag:

  • authentication failed

Это означает нарушение целостности данных.

3. Несоответствие enc алгоритма

Если библиотека не поддерживает указанный enc, например устаревший или нестандартный:

  • unsupported JWE algorithm

Отличие flattenedDecrypt от decrypt

В jose существует также метод decrypt, который работает с полным JWE JSON Serialization (с массивом recipients).

Разница:

  • flattenedDecrypt — один получатель, упрощённая структура
  • decrypt — множественные получатели, более универсальный формат

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

Внутреннее поведение алгоритма

На уровне реализации flattenedDecrypt выполняет:

  • парсинг структуры JWE
  • проверку обязательных полей
  • base64url декодирование компонентов
  • выбор криптографического backend (Web Crypto API или Node crypto)
  • выполнение unwrap ключа (если требуется)
  • выполнение AES-GCM или другого enc алгоритма
  • сверку authentication tag

Практические особенности использования

При работе с flattenedDecrypt важно учитывать:

  • объект JWE должен быть полностью валиден
  • отсутствующие поля (iv, tag, ciphertext) делают расшифровку невозможной
  • ключ должен строго соответствовать alg
  • для больших систем предпочтительно заранее валидировать структуру JWE

Работа в Node.js и браузере

Библиотека jose использует Web Crypto API, поэтому flattenedDecrypt работает:

  • в браузере без дополнительных зависимостей
  • в Node.js (версии с поддержкой crypto.webcrypto)

При этом поведение полностью унифицировано, что позволяет использовать один и тот же код в разных средах.

Типичные сценарии применения

flattenedDecrypt применяется в случаях:

  • API, возвращающие JWE в flattened формате
  • мобильные и браузерные клиенты
  • системы single-recipient encryption
  • безопасная передача токенов между сервисами
  • реализация защищённых сессий

Особенно часто используется в связке с compact encryption pipeline, где требуется минимальный размер и простая структура сообщения.