# Decrypting

> 

```ts
decrypt(token, key, options?)
```

Parses a compact JWE, validates header allowlists, decrypts the payload, and validates JWT claims (when the decrypted payload is an object).

## Parameters

<table>
<thead>
  <tr>
    <th>
      Name
    </th>
    
    <th>
      Type
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        token
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
      
       — the compact JWE
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        key
      </code>
    </td>
    
    <td>
      <code>
        CryptoKey | JWKSet | JWEDecryptJWK | string | Uint8Array | JWKLookupFunction
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options.algorithms
      </code>
    </td>
    
    <td>
      <code>
        KeyManagementAlgorithm[]
      </code>
      
       — allowlist for <code>
        alg
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options.encryptionAlgorithms
      </code>
    </td>
    
    <td>
      <code>
        ContentEncryptionAlgorithm[]
      </code>
      
       — allowlist for <code>
        enc
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options.validateClaims
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
      
       — force-skip claim validation
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options.forceUint8Array
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
      
       — return bytes instead of parsed JSON
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options.returnCek
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
      
       — include <code>
        cek
      </code>
      
       and <code>
        aad
      </code>
      
       in the result
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options.minIterations
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
      
       — PBES2 <code>
        p2c
      </code>
      
       floor (default <code>
        1000
      </code>
      
       per RFC 7518)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options.maxIterations
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
      
       — PBES2 <code>
        p2c
      </code>
      
       ceiling (default <code>
        1_000_000
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      Claim options
    </td>
    
    <td>
      Same as <code>
        verify()
      </code>
      
       — <code>
        audience
      </code>
      
      , <code>
        issuer
      </code>
      
      , <code>
        subject
      </code>
      
      , <code>
        maxTokenAge
      </code>
      
      , etc.
    </td>
  </tr>
</tbody>
</table>

`JWEDecryptJWK` is the private-side counterpart of [`JWEEncryptJWK`](/jwt/jwe/encrypting) — an `oct` JWK with a JWE symmetric `alg`, or a *private* asymmetric JWK whose `alg` is RSA-OAEP or ECDH-ES. `JWKSet` stays fully permissive (`JWK[]`); wire JWKS are heterogeneous and the runtime filters candidates per header.

Returns `Promise<{ payload, protectedHeader, cek?, aad? }>`.

## The simple case

```ts
const { payload, protectedHeader } = await decrypt(token, key);
```

If the key is a JWK with an `alg` field (as `generateJWK()` produces), unjwt infers the expected `alg` and proceeds. For passwords and raw bytes, `alg` is inferred when possible (passwords → PBES2 variants; `Uint8Array` → symmetric unwrap or `dir`).

## Algorithm allowlists

Two allowlists protect the decryptor:

- **algorithms** — which key-management (`alg`) values are acceptable.
- **encryptionAlgorithms** — which content-encryption (`enc`) values are acceptable.

When omitted, unjwt calls [`inferJWEAllowedAlgorithms`](/utilities#inferjweallowedalgorithms) to derive an `alg` allowlist from the key shape:

- `string` / `Uint8Array` password → `["PBES2-HS256+A128KW", "PBES2-HS384+A192KW", "PBES2-HS512+A256KW", "dir"]`
- Symmetric JWK with a specific wrap `alg` → that alg plus `"dir"`
- RSA private JWK → the matching `RSA-OAEP*` variants
- EC/OKP private JWK → the matching `ECDH-ES*` variants

If inference fails (lookup function, ambiguous key), pass explicitly:

```ts [explicit-allowlist.ts]
const { payload } = await decrypt(token, lookupFn, {
  algorithms: ["RSA-OAEP-256"],
  encryptionAlgorithms: ["A256GCM"],
});
```

<tip>

Setting `encryptionAlgorithms` is always a good idea — it's the only protection against a malicious token using a weaker-than-intended content cipher. `["A256GCM"]` is a strong default.

</tip>

## Dynamic key resolution

Same as `verify()` — pass a function that receives the header:

```ts [lookup.ts]
const { payload } = await decrypt(
  token,
  async (header, _rawToken) => {
    return await keyStore.get(header.kid!);
  },
  { algorithms: ["ECDH-ES+A256KW"], encryptionAlgorithms: ["A256GCM"] },
);
```

A `JWKLookupFunction` can return a single key or a `JWKSet`; if it returns a set, the per-kid / per-alg retry logic applies.

## JWKSet — rotation and multi-key decryption

When you pass a `JWKSet` (directly or returned from a lookup), unjwt selects candidates like it does on the sign side:

<steps level="4">

#### Token's header has `kid` — only keys with that `kid` are tried.

#### No `kid` — all keys whose `alg` matches the token's `alg` are candidates, tried in order.

#### No candidates — throws `ERR_JWK_KEY_NOT_FOUND` before any crypto runs.

</steps>

Useful when you rotate encryption keys: the new key wraps future tokens, but older tokens (wrapped with the previous key) still decrypt because both keys live in the set.

## PBES2 DoS protection

The PBES2 `p2c` (iteration count) header field is **attacker-controlled** — a malicious sender could craft a token with `p2c: 1_000_000_000` to burn your CPU on decryption. unjwt guards against this by default:

```ts
minIterations: 1000; // RFC 7518 §4.8.1.2 mandated floor
maxIterations: 1_000_000; // sane ceiling
```

Any token outside this range is rejected with `ERR_JWE_P2C_OUT_OF_BOUNDS` before PBKDF2 runs. Override cautiously:

```ts
const { payload } = await decrypt(token, "password", {
  minIterations: 100_000, // require at least this many iterations
  maxIterations: 2_000_000, // allow stronger-than-default tokens
});
```

## Returning the CEK

For custom verification flows (integrity checks over `aad`, manual key extraction), pass `returnCek: true`:

```ts
const { payload, cek, aad } = await decrypt(token, key, { returnCek: true });
// cek: Uint8Array — the derived Content Encryption Key
// aad: Uint8Array — the authenticated additional data (protected header bytes)
```

Most callers never need this.

## Claim validation

Identical to [JWS verify](/jwt/jws/verifying#claim-validation-options): when the decrypted payload is a JSON object, `exp`, `nbf`, and `iat` are validated automatically, plus any of `audience` / `issuer` / `subject` / `maxTokenAge` / `requiredClaims` / `typ` you pass.

Pass `validateClaims: false` to opt out (e.g. when you're decrypting arbitrary bytes rather than claim-bearing JSON).

## Payload typing

```ts [typed.ts]
interface Session {
  userId: string;
  role: "admin" | "user";
}

const { payload } = await decrypt<Session>(token, key);
payload.role; // "admin" | "user"
```

Force bytes:

```ts
const { payload } = await decrypt(token, key, { forceUint8Array: true });
payload instanceof Uint8Array; // true
```

## Full signature

```ts
interface JWEDecryptOptions extends JWTClaimValidationOptions {
  algorithms?: KeyManagementAlgorithm[];
  encryptionAlgorithms?: ContentEncryptionAlgorithm[];
  unwrappedKeyAlgorithm?: Parameters<typeof crypto.subtle.importKey>[2];
  keyUsage?: KeyUsage[];
  extractable?: boolean;
  forceUint8Array?: boolean;
  validateClaims?: boolean;
  returnCek?: boolean;
  minIterations?: number;
  maxIterations?: number;
}

interface JWEDecryptResult<T> {
  payload: T;
  protectedHeader: JWEProtectedHeader; // alg and enc required
  cek?: Uint8Array; // only when returnCek: true
  aad?: Uint8Array; // only when returnCek: true
}
```

## See also

- [Encrypting →](/jwt/jwe/encrypting) — the producer side.
- [Multi-recipient →](/jwt/jwe/multi-recipient) — for General JSON Serialization input.
- [Algorithms →](/jwt/jwe/algorithms) — picking `alg` and `enc`.
