# JWE — encrypted tokens

A **JSON Web Encryption** ([RFC 7516](https://www.rfc-editor.org/rfc/rfc7516)) is a JWT whose payload is **encrypted**: no one can read it without the right key, and no one can tamper with it without the tag failing.

Import:

```ts
import { encrypt, decrypt } from "unjwt/jwe";
// or from the flat barrel:
import { encrypt, decrypt } from "unjwt";
```

## Basic usage

The shortest path — password-based encryption (PBES2 under the hood):

```ts [basic.ts]
import { encrypt, decrypt } from "unjwt/jwe";

const token = await encrypt({ secret: "sensitive" }, "my-password");
const { payload } = await decrypt(token, "my-password");

console.log(payload);
// { secret: "sensitive" }
```

The token looks like a JWT (dotted segments) but the **third segment onwards is ciphertext** — base64url-decoding the payload gives you random bytes, not claims.

## How a JWE is built

Every JWE encrypts its payload in two steps:

<steps level="4">

#### **Content encryption** — a random Content Encryption Key (CEK) encrypts the payload with an authenticated cipher (AES-GCM or AES-CBC+HMAC-SHA2). The result is the ciphertext segment plus an IV and authentication tag.

#### **Key management** — the CEK itself is delivered to the recipient, either wrapped by a key they hold, or derived from a shared secret with them. That's what the **alg** header describes.

</steps>

So every JWE header has **two** algorithm fields:

- **alg** — how the CEK is delivered. Determines what kind of recipient key is required.
- **enc** — how the CEK encrypts the payload. Determines the content cipher.

```json
{ "alg": "PBES2-HS256+A128KW", "enc": "A128GCM", "p2s": "...", "p2c": 600000 }
```

You'll see these in every example below. Full list in [Algorithms](/jwt/jwe/algorithms).

## Choosing the `alg` family

<table>
<thead>
  <tr>
    <th>
      You have…
    </th>
    
    <th>
      Use <code>
        alg
      </code>
      
      …
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      A password string
    </td>
    
    <td>
      <code>
        PBES2-*
      </code>
      
       (inferred automatically for passwords)
    </td>
  </tr>
  
  <tr>
    <td>
      A shared secret key (both sides hold it)
    </td>
    
    <td>
      <code>
        A128KW
      </code>
      
      /<code>
        A256KW
      </code>
      
       (Key Wrap) or <code>
        dir
      </code>
      
       (direct)
    </td>
  </tr>
  
  <tr>
    <td>
      The recipient's RSA public key
    </td>
    
    <td>
      <code>
        RSA-OAEP-256
      </code>
      
       / <code>
        RSA-OAEP-512
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      The recipient's EC public key
    </td>
    
    <td>
      <code>
        ECDH-ES
      </code>
      
       or <code>
        ECDH-ES+A256KW
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      A prearranged CEK used directly as the key
    </td>
    
    <td>
      <code>
        dir
      </code>
    </td>
  </tr>
</tbody>
</table>

Algorithm inference handles most of this for you — passing a JWK with a `alg` field set picks the right path. See [Encrypting →](/jwt/jwe/encrypting).

## The three encryption patterns

The rest of JWE is just variations on three recurring shapes:

### 1. Password-based

One secret known to both parties. Simplest, slowest (by design).

```ts
const token = await encrypt({ data: "x" }, "my-password");
const { payload } = await decrypt(token, "my-password");
```

### 2. Shared key (symmetric)

A key that both parties already hold — you generated and distributed it out of band.

```ts
const key = await generateJWK("A256KW");
const token = await encrypt({ data: "x" }, key);
const { payload } = await decrypt(token, key);
```

### 3. Public-key (asymmetric)

The sender only needs the recipient's **public key**; only the holder of the matching private key can decrypt. Essential for end-to-end encryption.

```ts
const { publicKey, privateKey } = await generateJWK("RSA-OAEP-256");
// ... publicKey distributed out-of-band ...

const token = await encrypt({ data: "x" }, publicKey);
const { payload } = await decrypt(token, privateKey);
```

Elliptic-curve equivalent:

```ts
const { publicKey, privateKey } = await generateJWK("ECDH-ES+A256KW");
const token = await encrypt({ data: "x" }, publicKey);
const { payload } = await decrypt(token, privateKey);
```

See [ECDH-ES →](/jwt/jwe/ecdh-es) for the full end-to-end messaging story.

## Going further

- [Encrypting in depth →](/jwt/jwe/encrypting) — every `encrypt()` option.
- [Decrypting in depth →](/jwt/jwe/decrypting) — allowlists, PBES2 DoS bounds, key lookup.
- [Multi-recipient →](/jwt/jwe/multi-recipient) — one ciphertext, many recipients (JSON Serialization).
- [ECDH-ES and end-to-end encryption →](/jwt/jwe/ecdh-es) — the public-key workflow in detail.
- [Algorithms →](/jwt/jwe/algorithms) — picking `alg` and `enc`.
