# Encrypting

> 

```ts
encrypt(payload, key, options?)
```

Produces a **compact JWE** — five base64url segments: `<header>.<encryptedKey>.<iv>.<ciphertext>.<tag>`.

## Parameters

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

<tbody>
  <tr>
    <td>
      <code>
        payload
      </code>
    </td>
    
    <td>
      <code>
        string | Uint8Array | Record<string, unknown>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        key
      </code>
    </td>
    
    <td>
      <code>
        string | JWEEncryptJWK | CryptoKey | Uint8Array
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options.alg
      </code>
    </td>
    
    <td>
      <code>
        KeyManagementAlgorithm
      </code>
      
       — required when inference fails
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options.enc
      </code>
    </td>
    
    <td>
      <code>
        ContentEncryptionAlgorithm
      </code>
      
       — required when <code>
        alg = "dir"
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Others
    </td>
    
    <td>
      See <a href="#full-signature">
        Full signature
      </a>
      
       below
    </td>
  </tr>
</tbody>
</table>

`JWEEncryptJWK` narrows the JWK by family: an `oct` key with a JWE symmetric `alg` (AES-KW, AES-GCM, AES-GCM-KW, AES-CBC-HMAC, PBES2, or `"dir"`), or a public asymmetric JWK whose `alg` is RSA-OAEP (`JWK_RSA_Public<JWK_RSA_ENC>`) or ECDH-ES (`JWK_EC_Public<JWK_ECDH_ES>` / `JWK_OKP_Public<JWK_ECDH_ES>`). HMAC and other non-JWE algs are rejected at the type level.

Returns `Promise<string>` — the compact JWE.

## Algorithm inference

The shape of `key` determines `alg`, and then `alg` determines whether `enc` is inferable:

<table>
<thead>
  <tr>
    <th>
      <code>
        key
      </code>
      
       shape
    </th>
    
    <th>
      Inferred <code>
        alg
      </code>
    </th>
    
    <th>
      <code>
        enc
      </code>
      
       default?
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        string
      </code>
      
       (password)
    </td>
    
    <td>
      <code>
        PBES2-HS256+A128KW
      </code>
    </td>
    
    <td>
      <code>
        A128GCM
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        JWK
      </code>
      
       with <code>
        alg
      </code>
      
       field
    </td>
    
    <td>
      <code>
        key.alg
      </code>
    </td>
    
    <td>
      Depends on <code>
        alg
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        CryptoKey
      </code>
      
       with <code>
        usages: ["encrypt"]
      </code>
    </td>
    
    <td>
      Derived from algorithm context
    </td>
    
    <td>
      Usually required
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        CryptoKey | JWK_oct
      </code>
      
       with <code>
        alg = "dir"
      </code>
    </td>
    
    <td>
      <code>
        "dir"
      </code>
    </td>
    
    <td>
      <strong>
        Required
      </strong>
      
       — no default
    </td>
  </tr>
</tbody>
</table>

Pass `options.alg` / `options.enc` explicitly when inference is impossible.

## Key shapes

### Password (PBES2)

The shortest path — unjwt derives a CEK-wrapping key via PBKDF2:

```ts
const token = await encrypt({ data: "x" }, "my-password");
// Uses: alg = PBES2-HS256+A128KW, enc = A128GCM, p2c = 600_000
```

The `p2s` (salt) and `p2c` (iteration count) are written into the header so the decryptor can regenerate the same wrapping key.

<tip>

**p2c defaults to 600 000** — the [OWASP PBKDF2 recommendation](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2) for HMAC-SHA256. Lower it only for legacy interop. PBKDF2 is intentionally slow, so a password-protected token takes ~tens of milliseconds to produce on a modern laptop.

</tip>

### Symmetric JWK

A key generated by `generateJWK("A256KW")` or similar:

```ts
const aesKey = await generateJWK("A256KW"); // key.alg === "A256KW"
const token = await encrypt({ data: "x" }, aesKey); // alg inferred; enc defaults to A128GCM
```

### Asymmetric JWK — public key

For RSA-OAEP and ECDH-ES:

```ts
const { publicKey, privateKey } = await generateJWK("RSA-OAEP-256");
const token = await encrypt({ data: "x" }, publicKey);
// Recipient decrypts with privateKey
```

### Direct encryption — `dir`

With `alg = "dir"`, the provided key **is** the CEK. No wrapping happens; `encryptedKey` in the token is empty.

```ts
import { generateKey } from "unjwt/jwk";

const cek = await generateKey("A256GCM"); // CryptoKey suitable as CEK for A256GCM
const token = await encrypt({ data: "x" }, cek, { alg: "dir", enc: "A256GCM" });
```

When passing a `JWK_oct` directly, you can hint `enc` via the non-standard `jwk.enc` field (unjwt reads it):

```ts
const cekJwk = { ...(await generateJWK("A256GCM")), enc: "A256GCM" };
const token = await encrypt({ data: "x" }, cekJwk, { alg: "dir" });
```

`dir` produces the smallest token (the `encryptedKey` segment is empty) but requires both parties to hold the exact same CEK ahead of time.

## `expiresIn` — the same as JWS

If the payload is a JSON object:

```ts
await encrypt({ sub: "u1" }, key, { expiresIn: "1h" });
```

Accepted forms: `30`, `"30s"`, `"10m"`, `"2h"`, `"7D"`, `"1W"`, `"3M"`, `"1Y"`.

## Custom protected header

The JWE protected header is part of the AAD (Additional Authenticated Data) — anything you put there is bound to the ciphertext and can't be altered without invalidating the tag.

```ts
const token = await encrypt({ sub: "u1" }, key, {
  protectedHeader: {
    kid: "my-key",
    typ: "JWE", // default for object payloads
    cty: "application/json",
  },
});
```

The library manages these fields and rejects them in `protectedHeader`:

- `alg`, `enc` — set via `options.alg`/`options.enc`.
- `iv`, `tag` — computed during encryption.
- `p2s`, `p2c` — set for PBES2.
- `epk`, `apu`, `apv` — set for ECDH-ES.

## Custom CEK / IV

For test reproducibility, deterministic benchmarks, or interop scenarios:

```ts
import { secureRandomBytes } from "unjwt/utils";

const token = await encrypt({ data: "x" }, key, {
  cek: secureRandomBytes(32), // supply your own CEK
  contentEncryptionIV: secureRandomBytes(12), // supply your own IV
});
```

<warning>

**Never reuse** a CEK or IV across messages in production. The primary purpose of these options is testability. In normal use, let unjwt generate fresh random values.

</warning>

## PBES2 parameters

Override `p2c` or provide a fixed salt:

```ts
const token = await encrypt({ data: "x" }, "password", {
  p2s: secureRandomBytes(16),
  p2c: 100_000, // lower — only for legacy interop
});
```

Raising `p2c` on the sender side means proportionally more CPU for the decryptor too — unjwt enforces a ceiling by default (see [`maxIterations`](/jwt/jwe/decrypting#pbes2-dos-protection) on decrypt).

## ECDH-ES parameters

For `ECDH-ES` / `ECDH-ES+A*KW` keys:

```ts
const token = await encrypt({ data: "x" }, ecPublicKey, {
  enc: "A256GCM", // required at top level for bare "ECDH-ES"
  ecdh: {
    ephemeralKey: senderEphemeralKeypair, // override if you need a specific key
    partyUInfo: new TextEncoder().encode("alice@example.com"),
    partyVInfo: new TextEncoder().encode("bob@example.com"),
  },
});
```

`partyUInfo` / `partyVInfo` bind the derived key to specific sender/recipient identities (part of [NIST SP 800-56A](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar3.pdf) Concat KDF). They're optional, but they're how you prevent cross-context key reuse.

Full pattern: [ECDH-ES →](/jwt/jwe/ecdh-es).

## Full signature

```ts
interface JWEEncryptOptions {
  alg?: KeyManagementAlgorithm;
  enc?: ContentEncryptionAlgorithm;
  currentDate?: Date;
  expiresIn?: ExpiresIn;
  expiresAt?: Date;
  notBeforeIn?: ExpiresIn;
  notBeforeAt?: Date;
  protectedHeader?: StrictOmit<
    JWEHeaderParameters,
    "alg" | "enc" | "iv" | "tag" | "p2s" | "p2c" | "epk" | "apu" | "apv"
  >;
  keyManagementIV?: Uint8Array<ArrayBuffer>;
  p2s?: Uint8Array<ArrayBuffer>;
  p2c?: number;
  ecdh?: {
    ephemeralKey?:
      | CryptoKey
      | JWK_EC_Private
      | CryptoKeyPair
      | {
          publicKey: CryptoKey | JWK_EC_Public;
          privateKey: CryptoKey | JWK_EC_Private;
        };
    partyUInfo?: Uint8Array<ArrayBuffer>;
    partyVInfo?: Uint8Array<ArrayBuffer>;
  };
  cek?: Uint8Array<ArrayBuffer>;
  contentEncryptionIV?: Uint8Array<ArrayBuffer>;
}
```

## See also

- [Decrypting →](/jwt/jwe/decrypting) — the consumer side.
- [Algorithms →](/jwt/jwe/algorithms) — picking `alg` and `enc`.
- [ECDH-ES →](/jwt/jwe/ecdh-es) — end-to-end encryption.
- [Multi-recipient →](/jwt/jwe/multi-recipient) — one ciphertext, many recipients.
