Расшифровка и верификация: jwtDecrypt

jwtDecrypt выполняет расшифровку и проверку JWE (JSON Web Encryption) токенов в формате компактной сериализации, обеспечивая восстановление исходного полезного payload после криптографической обработки и одновременно проверяя корректность структуры токена и применённых алгоритмов.

В библиотеке jose этот механизм встроен в общий стек работы с JWT/JWS/JWE и опирается на строгую типизацию входных данных, поддержку современных криптографических алгоритмов и безопасное управление ключами.

JWE в компактном виде представляет собой строку из пяти частей:

  • protected header
  • encrypted key
  • initialization vector
  • ciphertext
  • authentication tag

Каждая часть кодируется в base64url и разделяется точками.

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

  • извлечение и декодирование заголовка
  • проверка алгоритма шифрования
  • извлечение зашифрованного ключа
  • расшифровка CEK (Content Encryption Key)
  • дешифровка payload
  • проверка целостности через authentication tag
  • возврат результата в виде объекта с payload и header

Сигнатура jwtDecrypt

В jose функция используется следующим образом:

import { jwtDecrypt } from 'jose'

Основной формат вызова:

const result = await jwtDecrypt(jwt, key, options)

Где:

  • jwt — строка JWE в compact формате
  • key — ключ расшифровки (KeyLike объект или JWK)
  • options — дополнительные параметры валидации

Ключи и криптографическая модель

jwtDecrypt работает только с симметричными или асимметричными ключами, совместимыми с алгоритмами JWE:

  • RSA-OAEP
  • RSA-OAEP-256
  • ECDH-ES
  • A256GCMKW
  • директивные AES-KW алгоритмы

Ключ может быть представлен в нескольких формах:

  • CryptoKey (Web Crypto API)
  • JWK (JSON Web Key)
  • KeyObject (Node.js crypto)

Пример импорта JWK:

import { importJWK } from 'jose'

const key = await importJWK(jwk, 'RSA-OAEP-256')

Основной процесс расшифровки

Внутри jwtDecrypt выполняются строго последовательные шаги:

1. Разбор структуры токена

Токен делится на 5 сегментов. При несоответствии формату выполнение прерывается.

2. Проверка protected header

Извлекается JOSE header, где проверяются:

  • alg (algorithm)
  • enc (encryption algorithm)
  • typ (тип токена)

Особое значение имеет enc, например:

  • A256GCM
  • A128CBC-HS256

3. Проверка допустимости алгоритма

Если алгоритм не входит в список разрешённых, операция отклоняется.

4. Расшифровка ключа контента (CEK)

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

  • unwrap ключа (если используется key wrapping)
  • или прямое соглашение ключа (например ECDH-ES)

5. Дешифровка ciphertext

Используется AES-GCM или CBC + HMAC в зависимости от enc.

6. Проверка integrity tag

Authentication tag проверяется до возврата результата. Любое несоответствие означает нарушение целостности данных.

Возвращаемое значение jwtDecrypt

Функция возвращает объект:

{
  payload: Uint8Array | string,
  protectedHeader: object,
  key: CryptoKey
}

payload обычно преобразуется в JSON объект:

const { payload, protectedHeader } = await jwtDecrypt(jwt, key)
const data = JSON.parse(new TextDecoder().decode(payload))

Валидация payload и типизация claims

jwtDecrypt не ограничивается только криптографией. Дополнительно может выполняться проверка claims:

  • exp (expiration time)
  • nbf (not before)
  • iat (issued at)
  • iss (issuer)
  • aud (audience)

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

const { payload } = await jwtDecrypt(jwt, key, {
  issuer: 'auth-service',
  audience: 'api-service'
})

Если хотя бы одно условие нарушено, выбрасывается ошибка.

Обработка ошибок

jwtDecrypt генерирует строго типизированные ошибки:

  • JWEInvalid
  • JWEDecryptionFailed
  • JWKSNoMatchingKey
  • JWTClaimValidationFailed

Каждая ошибка отражает конкретный этап процесса:

  • ошибки структуры токена
  • ошибки криптографии
  • ошибки ключей
  • ошибки claims

Пример обработки:

try {
  await jwtDecrypt(jwt, key)
} catch (e) {
  if (e.code === 'ERR_JWE_DECRYPTION_FAILED') {
    // некорректный токен или ключ
  }
}

Работа с алгоритмом ECDH-ES

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

  • отправитель генерирует ephemeral key
  • получатель использует свой private key
  • формируется shared secret
  • из него выводится CEK

jwtDecrypt автоматически обрабатывает этот процесс через Web Crypto API.

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

Часто ключи берутся из JWKS (JSON Web Key Set):

import { createRemoteJWKSet } from 'jose'

const JWKS = createRemoteJWKSet(new URL('https://example.com/.well-known/jwks.json'))

const { payload } = await jwtDecrypt(jwt, JWKS)

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

  • выбирает подходящий ключ
  • проверяет kid
  • применяет нужный алгоритм

Важные особенности безопасности

При работе jwtDecrypt учитываются критические принципы:

  • запрет слабых алгоритмов
  • защита от padding oracle атак
  • обязательная проверка authentication tag
  • изоляция ключевого материала в CryptoKey
  • отсутствие утечек plaintext до завершения проверки целостности

Отличие jwtDecrypt от decrypt

В jose существует также функция decrypt, которая работает с чистым JWE.

jwtDecrypt отличается тем, что:

  • предполагает JWT-совместимую структуру
  • поддерживает claims validation
  • ориентирован на токены авторизации
  • объединяет криптографию и проверку метаданных

Типичный поток обработки в приложении

В прикладной архитектуре jwtDecrypt используется в цепочке:

  • получение токена от клиента
  • выбор ключа из хранилища или JWKS
  • расшифровка payload
  • проверка claims
  • передача данных в бизнес-логику

Работа с Node.js и Web Crypto

В современных версиях jose используется Web Crypto API:

  • в Node.js через global crypto
  • в браузере через window.crypto.subtle

Это обеспечивает единый криптографический слой без зависимости от node-forge или legacy библиотек.

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

Производительность jwtDecrypt зависит от:

  • алгоритма шифрования (AES-GCM быстрее CBC)
  • размера payload
  • типа ключа (RSA медленнее ECDH)
  • количества проверок claims

ECDH-ES и AES-GCM считаются наиболее оптимальными для высоконагруженных систем.

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

import { jwtDecrypt, importJWK } from 'jose'

const key = await importJWK(jwk, 'A256GCM')

const { payload } = await jwtDecrypt(token, key, {
  issuer: 'auth-server',
  audience: 'service-api'
})

const data = JSON.parse(new TextDecoder().decode(payload))

Данный поток объединяет:

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

Поведение при некорректных данных

Любое нарушение целостности приводит к немедленному прекращению операции:

  • повреждённый ciphertext
  • неверный tag
  • несоответствие ключа
  • изменение protected header

plaintext не возвращается ни при каких условиях до успешной проверки integrity.