JSON Web Encryption (JWE): Compact Serialization & Algorithms

JSON Web Encryption (JWE) is the JOSE standard for encrypting a payload so only the intended recipient can read it—unlike JSON Web Signatures (JWS), which authenticate but leave the payload visible. JWE uses a two-key approach: a random Content Encryption Key (CEK) encrypts the payload, and a Key Management Algorithm (e.g., RSA-OAEP, AES-KW) wraps the CEK for the recipient. The most common algorithm pair is RSA-OAEP for key wrapping and AES-256-GCM for content encryption, providing 256-bit authenticated encryption. A JWE Compact Serialization is a single string with exactly 5 base64url-encoded parts separated by dots: Protected Header · Encrypted Key · Initialization Vector · Ciphertext · Authentication Tag. JWE is defined in RFC 7516, part of the JOSE suite alongside JWK (RFC 7517), JWA (RFC 7518), and JWS (RFC 7515). This guide covers the compact format, algorithm selection, JavaScript (jose library) and Python (jwcrypto) examples, and when to choose JWE over TLS.

Need to inspect a JWE token or decode its header? Jsonic's JWT decoder handles base64url decoding instantly.

Open JWT Decoder

JWE Compact Serialization Format

A JWE Compact Serialization token is a single ASCII string composed of exactly 5 base64url-encoded parts joined by 4 dots (.). Each part carries a distinct role in the encryption scheme. Understanding the structure lets you inspect and debug tokens using any base64url encoding tool — only the ciphertext part is truly opaque without the private key.

<Protected Header>.<Encrypted Key>.<IV>.<Ciphertext>.<Auth Tag>

Example (truncated for readability):
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ
.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe
.48V1_ALb6US04U3b
.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji
.XFBoMYUZodetObFIp

Part 1 — Protected Header: A JSON object base64url-encoded, always containing at minimum alg (key management algorithm) and enc (content encryption algorithm). Example decoded header: {"alg":"RSA-OAEP","enc":"A256GCM"}. Decode it with any base64url decoder to inspect the algorithm identifiers.

Part 2 — Encrypted Key: The CEK encrypted (wrapped) with the recipient's public or symmetric key, base64url-encoded. For ECDH-ES (direct key agreement), this part is an empty string because no encrypted key is transmitted — the CEK is derived via ECDH key exchange instead.

Part 3 — Initialization Vector (IV): A random 96-bit nonce used by AES-GCM. It must be unique per message — reusing an IV with the same key destroys GCM's security guarantees. Libraries generate a fresh random IV automatically on every encrypt call.

Part 4 — Ciphertext: The AES-GCM encrypted payload, base64url-encoded. Without the CEK (and therefore without the private key), this data is indistinguishable from random bytes.

Part 5 — Authentication Tag: A 128-bit GCM authentication tag that covers both the ciphertext and the Associated Additional Data (AAD), which includes the Protected Header. If either the ciphertext or the header is tampered with, tag verification fails and decryption is rejected.

// Decode the Protected Header (Part 1) in Node.js
const token = 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ...'
const parts = token.split('.')
const header = JSON.parse(Buffer.from(parts[0], 'base64url').toString())
console.log(header)
// { alg: 'RSA-OAEP', enc: 'A256GCM' }

Key Management Algorithms (alg)

The alg header parameter identifies how the Content Encryption Key (CEK) is protected for the recipient. Choosing the right key management algorithm depends on whether you control a public/private key pair (asymmetric) or share a secret key (symmetric). RFC 7518 defines at least 10 registered algorithm identifiers — the table below covers the most important ones.

alg identifierTypeDescriptionStatus
RSA-OAEPAsymmetricRSA-OAEP with SHA-1 MGF; 2048+ bit key recommendedCurrent
RSA-OAEP-256AsymmetricRSA-OAEP with SHA-256 MGF; stronger than RSA-OAEPRecommended
RSA1_5AsymmetricPKCS#1 v1.5 key wrap — padding oracle vulnerabilityDeprecated
ECDH-ESAsymmetricDirect key agreement via ECDH — no encrypted key part in tokenCurrent
ECDH-ES+A256KWAsymmetricECDH key agreement + AES-256 key wrapRecommended
A128KWSymmetricAES-128 key wrap (RFC 3394)Current
A256KWSymmetricAES-256 key wrap (RFC 3394)Current
A256GCMKWSymmetricAES-256-GCM key wrap — authenticated key wrappingRecommended
dirSymmetricDirect use of shared key as CEK — no key wrap stepUse with care

Recommendation for 2025: Use RSA-OAEP-256 for asymmetric key management — it uses SHA-256 in the mask generation function, providing stronger security than RSA-OAEP (SHA-1). RSA key size must be at least 2048 bits; 4096 bits is preferred for long-lived keys. For symmetric key management, use A256GCMKW — the GCM-based key wrap provides authenticated encryption of the CEK, which means any tampering with the encrypted key part is detected before decryption is attempted. Never use RSA1_5in new systems: PKCS#1 v1.5 is vulnerable to Bleichenbacher padding oracle attacks that allow an attacker to decrypt ciphertext with approximately 1 million queries to a decryption oracle.

Content Encryption Algorithms (enc)

The enc header parameter identifies the AEAD (Authenticated Encryption with Associated Data) algorithm used to encrypt the payload. All registered enc algorithms in RFC 7518 provide both confidentiality and integrity — decryption fails if the ciphertext has been tampered with. There are 5 registered values, split between AES-GCM and AES-CBC+HMAC constructions.

enc identifierKey sizeConstructionStatus
A128GCM128 bits (16 bytes)AES-128 in GCM mode — AEADCurrent
A192GCM192 bits (24 bytes)AES-192 in GCM mode — AEADCurrent
A256GCM256 bits (32 bytes)AES-256 in GCM mode — AEADRecommended
A128CBC-HS256256 bits (128-bit AES + 128-bit HMAC)AES-128-CBC + HMAC-SHA-256Legacy
A256CBC-HS512512 bits (256-bit AES + 256-bit HMAC)AES-256-CBC + HMAC-SHA-512Legacy

GCM vs CBC+HMAC: AES-GCM is an AEAD cipher that provides confidentiality and integrity in a single pass. The 128-bit authentication tag covers both the ciphertext and the Associated Additional Data (the Protected Header), so any bit flip anywhere in the token is detected. AES-CBC+HMAC splits the work: AES-CBC encrypts the payload, then HMAC-SHA covers the ciphertext. This is correct when implemented exactly per RFC 7516, but CBC mode requires careful PKCS#7 padding and the MAC-then-encrypt ordering must be followed precisely. Libraries handle this correctly, but CBC+HMAC has a larger attack surface than GCM.

Critical GCM constraint: The 96-bit IV (nonce) must never repeat under the same key. Repeating an IV with AES-GCM leaks key material and destroys confidentiality. All JOSE libraries generate a cryptographically random IV on every encrypt call — do not override this with a fixed nonce. The CEK itself is also randomly generated per message, making IV collision astronomically unlikely (2^96 possible nonces per unique 256-bit CEK).

Encrypt and Decrypt in JavaScript

The Web Crypto API (available in browsers, Node.js 16+, Deno, and Cloudflare Workers) does not expose a built-in JWE API. The jose npm library provides a complete JOSE implementation built on Web Crypto, supporting all registered JWE algorithms. It is the de facto standard for JWE in JavaScript with over 10 million weekly downloads.

npm install jose
import {
  CompactEncrypt,
  compactDecrypt,
  importSPKI,
  importPKCS8,
  generateKeyPair,
} from 'jose'

// ── Generate a key pair (RSA-OAEP-256, 2048 bits) ────────────────────
const { publicKey, privateKey } = await generateKeyPair('RSA-OAEP-256', {
  modulusLength: 2048,
  extractable: true,
})

// ── Encrypt ──────────────────────────────────────────────────────────
const payload = new TextEncoder().encode(
  JSON.stringify({ userId: 42, role: 'admin', iat: Date.now() })
)

const jwe = await new CompactEncrypt(payload)
  .setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM' })
  .encrypt(publicKey)

console.log(jwe)
// eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.<...>.<...>.<...>.<...>

// ── Decrypt ──────────────────────────────────────────────────────────
const { plaintext, protectedHeader } = await compactDecrypt(jwe, privateKey)

console.log(protectedHeader)
// { alg: 'RSA-OAEP-256', enc: 'A256GCM' }

const claims = JSON.parse(new TextDecoder().decode(plaintext))
console.log(claims)
// { userId: 42, role: 'admin', iat: 1715000000000 }

To load an existing RSA key from PEM strings (e.g., loaded from environment variables or a key management service), use importSPKI for the public key and importPKCS8 for the private key:

// Load keys from PEM strings
const publicKey  = await importSPKI(process.env.JWE_PUBLIC_KEY_PEM!,  'RSA-OAEP-256')
const privateKey = await importPKCS8(process.env.JWE_PRIVATE_KEY_PEM!, 'RSA-OAEP-256')

// Encrypt with symmetric key (A256GCMKW + A256GCM)
import { createSecretKey } from 'crypto'

const symmetricKey = createSecretKey(
  Buffer.from(process.env.JWE_SECRET_KEY_HEX!, 'hex') // 32-byte (256-bit) key
)

const jwe = await new CompactEncrypt(payload)
  .setProtectedHeader({ alg: 'A256GCMKW', enc: 'A256GCM' })
  .encrypt(symmetricKey)

Note that payload must be a Uint8Array — use new TextEncoder().encode(string) for text payloads. compactDecrypt returns plaintext as a Uint8Array; use new TextDecoder().decode(plaintext) to convert back to a string. Use decode a JWT for decoding the header without the private key, and the JSON Web Key (JWK) guide for distributing public keys as JWK sets.

JWE vs TLS: When to Use Each

TLS and JWE operate at different layers of the security stack and solve different problems. Understanding the distinction prevents both under-protection (relying on TLS when end-to-end encryption is needed) and over-engineering (adding JWE complexity where TLS alone suffices). The decision comes down to 3 questions: who are the intermediaries, where is the payload stored, and what is the threat model?

TLS (HTTPS) encrypts the channel: Encryption exists only during transit between 2 endpoints. At each TLS-terminating hop — reverse proxy, load balancer, API gateway, CDN edge node — the payload is decrypted and re-encrypted. The payload is in plaintext inside every server it touches. TLS is mandatory for all network communication and provides protection against passive network eavesdropping and active man-in-the-middle attacks on the wire.

JWE encrypts the payload: The payload remains encrypted end-to-end regardless of how many systems handle it. Reverse proxies, message brokers, logging pipelines, and databases all see only opaque ciphertext. Only the entity holding the private key can decrypt.

Use JWE when:

  • Storing encrypted tokens in cookies or localStorage so the browser or user cannot read the claims
  • Passing sensitive data (PII, financial data) through intermediaries — message queues, microservices, third-party processors
  • Adding per-field encryption to a JSON Web Token (JWT) that travels through untrusted systems
  • Meeting compliance requirements (HIPAA, PCI-DSS) that mandate end-to-end encryption of specific data fields

TLS only is sufficient when: Internal service-to-service calls run on a private network with mutual TLS (mTLS), there are no intermediaries that should be excluded from reading the payload, and data is not stored in plaintext in external systems. Roughly 95% of APIs need TLS; approximately 5% additionally benefit from JWE. Adding JWE when it is not needed adds latency (RSA-OAEP-256 encryption on a 2048-bit key takes roughly 1–2 ms), key management overhead, and code complexity without a corresponding security gain.

Key Terms and Definitions

JWE (JSON Web Encryption)
A JOSE standard (RFC 7516) that defines a compact, URL-safe representation of an encrypted payload, providing confidentiality for the enclosed content using asymmetric or symmetric cryptography.
CEK (Content Encryption Key)
A randomly generated symmetric key, created fresh for each JWE token, that is used to encrypt the plaintext payload with the content encryption algorithm (e.g., AES-256-GCM); the CEK itself is then wrapped (encrypted) by the key management algorithm.
Key Wrapping
The process of encrypting the CEK using the recipient's public key (for asymmetric algorithms like RSA-OAEP-256) or a shared symmetric key (for algorithms like A256GCMKW), producing the Encrypted Key part of the JWE compact token.
AEAD (Authenticated Encryption with Associated Data)
A cipher mode that simultaneously provides both confidentiality (the ciphertext reveals nothing about the plaintext) and integrity (any modification to the ciphertext or associated data is detected and rejected); AES-GCM is the primary AEAD algorithm used in JWE.
Protected Header
A JSON object in the JWE token, base64url-encoded as the first of the 5 compact parts, that specifies the algorithm identifiers (alg and enc) and is included as Associated Additional Data (AAD) in the GCM authentication tag computation, ensuring the header cannot be tampered with without detection.
JOSE (JSON Object Signing and Encryption)
A suite of IETF standards for representing cryptographic operations in JSON, comprising JWK (RFC 7517, key representation), JWA (RFC 7518, algorithm identifiers), JWS (RFC 7515, signing), JWE (RFC 7516, encryption), and JWT (RFC 7519, claim tokens).
Initialization Vector (IV)
A random value, 96 bits for AES-GCM, that must be unique for every encrypt operation under the same key; included as the third part of the JWE compact serialization and required by the decryption algorithm to reconstruct the keystream.

Frequently asked questions

What is the difference between JWE and JWT?

JWT (JSON Web Token) is a container format for transmitting claims between parties. Most JWTs are secured using JWS (JSON Web Signature), which signs the payload so anyone can read the claims but can verify they were not tampered with. JWE encrypts the entire payload so only the holder of the private key can read the contents. You can combine both: a signed JWT (JWS) can be nested inside a JWE to produce an encrypted and signed token — called a nested JWT. In practice, the vast majority of JWTs in use today are signed (JWS), not encrypted. Use JWE when the JWT claims themselves contain sensitive data — such as personal identifiers or financial data — that should not be readable by intermediaries, third parties, or the end user who holds the token. See the JSON Web Token (JWT) guide for a full explanation of the JWT container format and claim structure.

How many parts does a JWE compact token have?

A JWE Compact Serialization token has exactly 5 parts separated by 4 dots: (1) Protected Header — a base64url-encoded JSON object containing the alg and enc algorithm identifiers; (2) Encrypted Key — the CEK wrapped with the recipient's key, base64url-encoded; (3) Initialization Vector — a random nonce, typically 96 bits for GCM modes; (4) Ciphertext — the encrypted payload; (5) Authentication Tag — the 128-bit GCM tag providing integrity verification. By contrast, a JWS (signed JWT) has only 3 parts: header, payload, and signature. If you receive a token and want to count the parts, split on . — 3 parts means JWS, 5 parts means JWE. You can inspect the Protected Header of either format by decoding the first part with a base64url decoder.

What algorithms should I use for JWE in 2025?

For asymmetric (public key) encryption, use RSA-OAEP-256 as the key management algorithm (alg) paired with A256GCM as the content encryption algorithm (enc). RSA-OAEP-256 uses SHA-256 for the OAEP mask generation function, providing stronger security than the older RSA-OAEP (SHA-1) variant. For symmetric (shared secret) encryption, use A256GCMKW paired with A256GCM — both key wrap and content encryption use AES-GCM, providing authenticated encryption at every layer. Avoid RSA1_5 entirely: PKCS#1 v1.5 key wrapping is vulnerable to Bleichenbacher padding oracle attacks and has been deprecated by major security standards bodies. Avoid A128CBC-HS256 in new systems where possible — prefer GCM-based AEAD constructions. For elliptic curve scenarios, ECDH-ES+A256KW is a strong modern choice that avoids RSA entirely.

Can I decrypt a JWE token without the private key?

No. The security guarantee of JWE is that the Content Encryption Key (CEK) is wrapped (encrypted) using the recipient's public key. Only the corresponding private key can unwrap the CEK. Without the private key, you cannot recover the CEK, and without the CEK you cannot decrypt the ciphertext. This is by design: even if an attacker intercepts the JWE token — for example from a cookie, localStorage, or a message queue — they cannot read the plaintext payload. This is the fundamental difference between JWE and a signed JWT (JWS): a JWS payload is base64url-encoded and visible to anyone who holds the token, while a JWE payload is encrypted and opaque. The only entity that can decrypt a JWE is the one that holds the private key corresponding to the public key used during encryption. Manage private keys using a key management service (KMS) and rotate them on a regular schedule — leaked private keys compromise all past JWE tokens encrypted to that key.

What is the difference between JWE and JWS?

JWS (JSON Web Signature) and JWE (JSON Web Encryption) are both part of the JOSE suite but provide different security properties. JWS signs the payload: the payload is base64url-encoded and visible to anyone who has the token, but a recipient can verify that the payload has not been modified since it was signed — JWS provides integrity and authentication. JWE encrypts the payload: the payload is fully encrypted and opaque; only the intended recipient holding the private key can decrypt and read it — JWE provides confidentiality. A useful analogy: JWS is a transparent envelope with a wax seal (visible contents, tamper-evident), while JWE is an opaque locked box (contents hidden, only key holder can open). Most OAuth 2.0 access tokens and OIDC ID tokens are JWS. JWE is used when the token itself contains sensitive data that must not be readable by the bearer. See the JSON Web Key (JWK) guide for how public keys are distributed for both JWS verification and JWE encryption.

Is JWE the same as HTTPS encryption?

No. HTTPS/TLS encrypts the transport channel between a client and server — the encryption exists only for the duration of the network hop and is terminated at each endpoint, including reverse proxies, load balancers, and API gateways. Once the payload arrives at a server, TLS is stripped and the data is in plaintext within the server's memory and storage. JWE encrypts the payload object itself, providing end-to-end encryption that persists regardless of how many intermediaries the message passes through — reverse proxies, message queues, databases, and logging systems all see only the encrypted ciphertext. Use TLS always for transport security — this is not optional. Use JWE additionally when per-payload confidentiality is required beyond the transport layer: storing tokens in cookies or databases, passing sensitive data through untrusted intermediaries, or meeting compliance mandates that require field-level encryption. TLS and JWE are complementary, not competing — use both when the threat model demands it.

Inspect JWE and JWT tokens with Jsonic

Use Jsonic's JWT Decoder to decode and inspect the Protected Header of any JWE or JWS token. Paste a compact serialization token to see the algorithm identifiers, token parts, and decoded header instantly — no private key required.

Open JWT Decoder