Подписанный JWT против зашифрованного JWT

JWT в экосистеме JavaScript чаще всего используется через спецификацию JOSE (JSON Object Signing and Encryption), которая определяет два принципиально разных механизма защиты данных: подписывание (JWS — JSON Web Signature) и шифрование (JWE — JSON Web Encryption). В библиотеке jose оба подхода реализованы через единый набор API, но с разными криптографическими гарантиями и архитектурными последствиями.

Подписанный JWT решает задачу проверки целостности и подлинности данных. Его содержимое не скрывается, а лишь защищается от подделки.

Структура JWS включает три части:

  • Header (заголовок с алгоритмом)
  • Payload (данные)
  • Signature (подпись)

Payload остаётся в открытом виде после base64url-декодирования. Это означает, что любой, кто получает токен, может прочитать его содержимое без ключа.

Основная задача подписи:

  • подтверждение, что токен создан доверенной стороной
  • защита от изменения данных

Пример создания подписанного JWT через jose

import { SignJWT } from 'jose'

const secret = new TextEncoder().encode('super-secret-key')

const token = await new SignJWT({ userId: 123, role: 'admin' })
  .setProtectedHeader({ alg: 'HS256' })
  .setIssuedAt()
  .setExpirationTime('2h')
  .sign(secret)

console.log(token)

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

Проверка подписи

import { jwtVerify } from 'jose'

const secret = new TextEncoder().encode('super-secret-key')

const { payload } = await jwtVerify(token, secret)

console.log(payload)

Если подпись изменена или токен повреждён, проверка завершится ошибкой.

Шифрованный JWT (JWE)

Шифрованный JWT решает другую задачу — конфиденциальность данных. В отличие от JWS, payload не виден без ключа.

JWE состоит из пяти частей:

  • Protected Header
  • Encrypted Key
  • Initialization Vector
  • Ciphertext
  • Authentication Tag

В отличие от подписанного токена, содержимое полностью скрыто.

Когда используется JWE:

  • хранение чувствительных данных (персональные данные, документы)
  • передача секретной информации между сервисами
  • защита payload от просмотра на клиенте

Пример создания зашифрованного JWT

import { EncryptJWT } from 'jose'

const key = new TextEncoder().encode('encryption-key-32-bytes-minimum!!')

const token = await new EncryptJWT({ userId: 123, card: '4111111111111111' })
  .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
  .setIssuedAt()
  .setExpirationTime('2h')
  .encrypt(key)

console.log(token)

Здесь используется алгоритм A256GCM, обеспечивающий одновременно шифрование и проверку целостности.

Расшифровка JWE

import { compactDecrypt } from 'jose'

const key = new TextEncoder().encode('encryption-key-32-bytes-minimum!!')

const { plaintext } = await compactDecrypt(token, key)

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

console.log(payload)

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

Ключевое различие подходов

Подписанный JWT и зашифрованный JWT решают разные задачи и часто путаются из-за внешнего сходства формата.

Подписанный JWT (JWS)

  • данные читаемы
  • защищена целостность
  • используется чаще всего для авторизации
  • быстрее по производительности
  • меньше накладных расходов

Зашифрованный JWT (JWE)

  • данные скрыты
  • защищена конфиденциальность
  • сложнее в обработке
  • требует больше вычислений
  • используется для чувствительных данных

Комбинированные сценарии

В сложных системах применяется комбинация:

  • сначала создаётся JWS (подпись)
  • затем результат шифруется как JWE

Такой подход обеспечивает одновременно:

  • проверку подлинности
  • защиту содержимого

Алгоритмы в jose

Библиотека поддерживает разные криптографические схемы:

Симметричные:

  • HS256
  • A256GCM (для шифрования)

Асимметричные:

  • RS256 (RSA)
  • ES256 (ECDSA)
  • RSA-OAEP (для шифрования ключей в JWE)

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

  • приватный ключ хранится на сервере
  • публичный используется для проверки

Типичная ошибка архитектуры

Использование JWS там, где требуется конфиденциальность данных, приводит к утечке информации через payload. JWT часто хранится в браузере или логируется прокси-серверами, поэтому любая незашифрованная информация становится доступной вне контроля приложения.

JWE, в свою очередь, иногда применяют без необходимости, что усложняет систему и увеличивает нагрузку без реальной выгоды, если данные не являются чувствительными.

Внутреннее устройство jose

Библиотека jose построена как модульный набор функций:

  • SignJWT — создание JWS
  • jwtVerify — проверка JWS
  • EncryptJWT — создание JWE
  • compactDecrypt — расшифровка JWE

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

Форматы компактной сериализации

Оба типа JWT используют компактный формат:

  • JWS: header.payload.signature
  • JWE: header.encryptedKey.iv.ciphertext.tag

Различие в количестве сегментов отражает криптографическую сложность операций.

Производственные сценарии

Подписанные JWT чаще применяются в:

  • OAuth 2.0
  • сессиях API
  • микросервисной авторизации

Зашифрованные JWT используются в:

  • финансовых системах
  • медицинских данных
  • передаче PII (personal identifiable information)

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

Подписанный JWT:

  • можно декодировать без ключа
  • нельзя изменить незаметно

Зашифрованный JWT:

  • нельзя прочитать без ключа
  • нельзя изменить без нарушения целостности

Оба механизма могут использоваться вместе, но решают разные уровни задачи безопасности: доверие и конфиденциальность.