Как работать с JWE и NestedJWT?
JSON Web Encryption
Полезные ссылки:
- Спецификация rfc7516;
- Онлайн инструмент для работы с JWE;
- Библиотеки для работы с JWE: nimbus-jose-jwt, справочник openid;
- Информация для разработчиков на PHP: web-token.spomky-labs.com
JWE — разновидность JWT, при которой полезная нагрузка подлежит шифрованию.
Структурно JWE состоит из пяти частей:
- header — заголовок;
- encrypted key — зашифрованный ассиметричным алгоритмом ключ;
- initialization vector — вектор инициализации;
- ciphertext — зашифрованная симметричным алгоритмом полезная нагрузка;
- authentication tag — тег аутентификации.
Каждая часть кодируется при помощи алгоритма base64url. Закодированные части соединяются друг с другом через знак «.»
BASE64URL(JOSE Header) + '.' +
BASE64URL(JWE Encrypted Key) + '.' +
BASE64URL(JWE Initialization Vector) + '.' +
BASE64URL(JWE Ciphertext) + '.' +
BASE64URL(JWE Authentication Tag)
Формирование заголовка
Аналогично JWT, это JSON-объект, содержащий сведения о токене, который кодируется при помощи алгоритма base64url.
| Параметр | Описание | Обязательный | Пример |
|---|---|---|---|
| alg | Указание алгоритма, с помощью которого зашифрован encrypted key. Рекомендуется использовать RSA-OAEP-256. | Да | RSA-OAEP-256 |
| kid | Идентификатор ключа, с помощью которого выполнено ассиметричное шифрование. Значение должно совпадать со значением одноимённого параметра в публичном ключе с меткой «use»:«enc», опубликованным на jwks_endpoint. | Да | rsa1 |
| enc | Указание алгоритма, с помощью которого зашифрован ciphertext. Рекомендуется использовать A256GCM. | Да | A256GCM |
| typ | Тип контента, которым является итоговый токен. Поддерживается только JWT. | Да | JWT |
| cty | Дополнительная информация, описывающая тип контента. Обязательно используется для NestedJWT со значением «JWT». | Нет | JWT |
Пример JSON Header:
{
"alg": "RSA-OAEP-256",
"kid": "rsa1",
"enc": "A256GCM",
"typ": "JWT"
}
Пример JWE Header:
eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJraWQiOiJyc2ExIiwiZW5jIjoiQTI1NkdDTSIsInR5cCI6IkpXVCJ9
Шифрование
В общем виде порядок шифрования состоит из следующих шагов:
- Сформировать json header;
- Сгенерировать случайное значение для encrypted key;
- Зашифровать сгенерированное значение публичным ключом получателя с использованием alg из json header;
- Сгенерировать initialization vector;
- Сгенерировать Authentication Tag;
- Зашифровать полезную нагрузку с помощью encrypted key, initialization vector и Authentication Tag с использованием enc из json header;
- Сформировать итоговый JWE из отдельно закодированных в base64url частей с разделителем «.».
Пример на PHP:
private function buildJWE(string $payload, string $keyEncryptionAlg, string $contentEncryptionAlg): string
{
$jweBuilder = $this->jweBuilderFactory->create(
array_keys($this->config['jwt']['key_encryption_algorithms']),
array_keys($this->config['jwt']['content_encryption_algorithms']),
array_keys($this->config['jwt']['compressions'])
);
$jwe = $jweBuilder->create()
->withPayload($payload)
->withSharedProtectedHeader([
'alg' => $keyEncryptionAlg,
'enc' => $contentEncryptionAlg,
])
->addRecipient(JWKFactory::createFromValues($this->getJWK()))
->build();
$jweSerializer = $this->jweSerializerManagerFactory->create([EncCompactSerializer::NAME]);
return $jweSerializer->serialize(EncCompactSerializer::NAME, $jwe);
}
Расшифровка
В общем виде порядок расшифровки состоит из следующих шагов:
- Декодировать из base64url значения элементов;
- Вычислить encrypted key — расшифровать приватным ключом с использованием alg из json header;
- Вычислить полезную нагрузку — расшифровать Ciphertext с использованием encrypted key, initialization vector и authentication tag с применением метода, указанного в enc из json header.
Пример на PHP:
private function getJWEPayload(): string
{
// Our key.
$encKey = $this->getJWKFromKey('private', 'enc');
$jweLoader = $this->jweLoaderFactory->create(
['jwe_compact'],
[$this->jwe->getSharedProtectedHeaderParameter('alg')],
[$this->jwe->getSharedProtectedHeaderParameter('enc')],
['DEF']
);
$jwe = $jweLoader->loadAndDecryptWithKey($this->token, $encKey, $recipient);
return $jwe->getPayload();
}
Nested JWT
Частный случай JWE с единственным отличием — в качестве полезной нагрузки выступает не JSON-объект, а предварительно сформированный и подписанный JWT. При этом исходный JSON-объект становится полезной нагрузкой вложенного JWT.