
# generateJWK() / generateKey()

> Make a fresh key for any JWS or JWE algorithm. `generateJWK` returns JSON ready to store; `generateKey` returns Web Crypto objects unless you ask for JSON.

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

const secret = await generateJWK("HS256");
// {
//   alg: "HS256",
//   ext: true,
//   k: "sQJeXE6RCGCL0PQ-oG9nK3HW…",
//   key_ops: ["sign", "verify"],
//   kty: "oct",
//   kid: "Z7Vbm5-ga-jt60sVMc9u6T5_V5uPrO_dUQNw-6uVeBM",
// }

const { privateKey, publicKey } = await generateJWK("ES256");
// privateKey: { crv: "P-256", d: "V5UCmgY_…", x: "mGQhkJr2…", y: "m-i6Bvx2…", kty: "EC",
//               key_ops: ["sign"], ext: true, alg: "ES256", kid: "vlj-lU5hMsOLA09r…" }
// publicKey:  the same without `d`, with key_ops: ["verify"] and the same kid
```

Reach for `generateJWK` by default. Use `generateKey` when you want a `CryptoKey` that never leaves the process (see [Non-extractable keys](#non-extractable-keys)).

## Signatures

```ts
function generateJWK<TAlg extends GenerateKeyAlgorithm, Kid extends string | false = string>(
  alg: TAlg,
  options?: {
    "kid"?: Kid;
    "keyUsage"?: KeyUsage[];
    "modulusLength"?: number;
    "publicExponent"?: Uint8Array;
    "namedCurve"?: "P-256" | "P-384" | "P-521" | "X25519" | "Ed25519";
    // JWK metadata merged onto the result
    "use"?: string;
    "x5c"?: string[];
    "x5t"?: string;
    "x5t#S256"?: string;
    "x5u"?: string;
  },
): Promise<GeneratedJWKMap<Kid>[TAlg]>;

function generateKey<TAlg extends GenerateKeyAlgorithm>(
  alg: TAlg,
  options?: {
    toJWK?: boolean;
    extractable?: boolean;
    kid?: string | false;
    keyUsage?: KeyUsage[];
    modulusLength?: number;
    publicExponent?: Uint8Array;
    namedCurve?: "P-256" | "P-384" | "P-521" | "X25519" | "Ed25519";
  },
): Promise<GeneratedKeyMap[TAlg] | GeneratedJWKMap[TAlg]>;
```

`GenerateKeyAlgorithm` is every JWS and JWE algorithm except `"none"`, `"dir"` and the PBES2 family. A password-based key comes from [`deriveJWKFromPassword()`](/jwk/password).

## Options

| Option                                 | Type              | Default                               | What it does                                                                                                                                                             |
| -------------------------------------- | ----------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `kid`                                  | `string \| false` | the RFC 7638 thumbprint               | A string is set verbatim. `false` sets no `kid`. Anything else is `ERR_INVALID_OPTION`. On `generateKey` it is read only with `toJWK: true`.                             |
| `toJWK`                                | `boolean`         | `false`                               | `generateKey` only. Return JWKs instead of Web Crypto objects.                                                                                                           |
| `extractable`                          | `boolean`         | `true`                                | `generateKey` only. Ignored with `toJWK`, which must export the key.                                                                                                     |
| `keyUsage`                             | `KeyUsage[]`      | derived from `alg`                    | Narrows the usages. A usage the algorithm cannot serve, or a list that leaves one half of a pair with nothing, is `ERR_INVALID_OPTION`.                                  |
| `modulusLength`                        | `number`          | `2048`                                | RSA only. Below 2048 bits is `ERR_INVALID_OPTION` ([RFC 7518 §3.3](https://www.rfc-editor.org/rfc/rfc7518#section-3.3)).                                                 |
| `publicExponent`                       | `Uint8Array`      | `0x010001`                            | RSA only. Big-endian bytes.                                                                                                                                              |
| `namedCurve`                           | see signature     | `"P-256"` for EC, `"Ed25519"` for OKP | `ES256`/`ES384`/`ES512` fix their curve, so a different one is `ERR_INVALID_OPTION`. `ECDH-ES*` picks from P-256, P-384, P-521 and X25519. `EdDSA` accepts Ed25519 only. |
| `use`, `x5c`, `x5t`, `x5t#S256`, `x5u` | JWK members       | none                                  | `generateJWK` only. Merged onto the key (both halves of a pair). `alg`, `kty`, `key_ops`, `ext` and key material are reserved and throw `ERR_INVALID_OPTION`.            |

## Return shape

The algorithm decides the shape. Every JWK carries `alg` and, unless `kid: false`, a `kid`.

| Algorithm                                         | `generateKey(alg)`                             | `generateJWK(alg)` or `generateKey(alg, { toJWK: true })`       |
| ------------------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------- |
| `HS*`, `A*KW`, `A*GCM`, `A*GCMKW`                 | `CryptoKey`                                    | one `oct` JWK                                                   |
| `A128CBC-HS256`, `A192CBC-HS384`, `A256CBC-HS512` | `Uint8Array` (Web Crypto has no such key type) | one `oct` JWK                                                   |
| `RS*`, `PS*`, `RSA-OAEP*`                         | `CryptoKeyPair`                                | `{ privateKey, publicKey }`, `kty: "RSA"`                       |
| `ES*`                                             | `CryptoKeyPair`                                | `{ privateKey, publicKey }`, `kty: "EC"`                        |
| `Ed25519`, `EdDSA`                                | `CryptoKeyPair`                                | `{ privateKey, publicKey }`, `kty: "OKP"`                       |
| `ECDH-ES*`                                        | `CryptoKeyPair`                                | `{ privateKey, publicKey }`, `kty: "EC"`, or `"OKP"` for X25519 |

The table is two exported types, `GeneratedKeyMap` and `GeneratedJWKMap`, keyed by algorithm. A literal `alg` narrows the result, so no cast is needed:

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

const a = await generateJWK("HS256");
// typeof a: JWK_oct<"HS256"> & { alg: "HS256"; kid: string }

const b = await generateJWK("HS256", { kid: false });
// typeof b: JWK_oct<"HS256"> & { alg: "HS256" }

const c = await generateJWK("ES256");
// typeof c: {
//   privateKey: JWK_EC_Private<"ES256"> & { alg: "ES256"; kid: string };
//   publicKey: JWK_EC_Public<"ES256"> & { alg: "ES256"; kid: string };
// }

const d = await generateKey("HS256"); // typeof d: CryptoKey
const e = await generateKey("ES256"); // typeof e: CryptoKeyPair
const f = await generateKey("A128CBC-HS256"); // typeof f: Uint8Array<ArrayBuffer>
const g = await generateKey("ES256", { toJWK: true }); // typeof g: GeneratedJWKMap["ES256"]
```

A non-literal algorithm (a variable typed `GenerateKeyAlgorithm`) returns the union of its entries.

## The default `kid` is a thumbprint

A `kid` is the name a token carries in its header so a verifier can find the right key. When you do not pass one, the key is named by its [RFC 7638](https://www.rfc-editor.org/rfc/rfc7638) thumbprint: a SHA-256 digest of the key's required public members. Two consequences follow. Both halves of a pair get the same `kid`, because the private members are not part of the digest. And two different keys never share a `kid` by accident.

```ts
import { generateJWK, thumbprint } from "unjwt/jwk";

const key = await generateJWK("HS256");
key.kid === (await thumbprint(key)); // true

await generateJWK("HS256", { kid: "hmac-2026-10" }); // kid: "hmac-2026-10"
await generateJWK("HS256", { kid: false }); // no kid member at all
```

`thumbprint(jwk, { hash })` is exported too, with `hash` one of `"SHA-256"` (default), `"SHA-384"` or `"SHA-512"`. Passing your own `kid`, or `false`, skips the digest.

## Wrapping keys get the `alg` you asked for

`generateJWK("A256GCMKW")` returns a key with `alg: "A256GCMKW"`. Web Crypto itself only knows AES-GCM, so exporting the same key through [`exportKey()`](/jwk/import-export) labels it `A256GCM`, the content encryption role. Mint key-wrapping keys with `generateJWK` so the label matches the job.

## Non-extractable keys

`generateKey` defaults to `extractable: true` so keys can be exported. For a long-lived private key that should never leave the process, ask for a non-extractable pair:

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

const pair = await generateKey("ES256", { extractable: false });
pair.privateKey.extractable; // false
pair.publicKey.extractable; // true, Web Crypto always keeps public keys exportable
```

The private `CryptoKey` still signs, but its material cannot be read back out.

## Errors

| Cause                                                                                  | `code`                |
| -------------------------------------------------------------------------------------- | --------------------- |
| An algorithm with no generated key (`PBES2-*`, `dir`, `none`, anything unknown)        | `ERR_JWK_UNSUPPORTED` |
| `EdDSA` with a curve other than Ed25519, or `ECDH-ES*` with an unknown curve           | `ERR_JWK_UNSUPPORTED` |
| `modulusLength` below 2048, a malformed `publicExponent`                               | `ERR_INVALID_OPTION`  |
| A `namedCurve` that contradicts `ES*` or `Ed25519`                                     | `ERR_INVALID_OPTION`  |
| A `keyUsage` the algorithm cannot serve, or one that empties a pair half               | `ERR_INVALID_OPTION`  |
| A `kid` that is neither a string nor `false`, or a reserved JWK member in the metadata | `ERR_INVALID_OPTION`  |

```ts
await generateJWK("ES256", { keyUsage: ["sign"] });
// JWTError ERR_INVALID_OPTION: "keyUsage" leaves the public half of a ES256 key pair with no usage; include one of verify.
```

## See also

- [importKey() / exportKey()](/jwk/import-export) to convert what you generated.
- [JWK Sets](/jwk/sets) to publish public halves and rotate.
- [JWS algorithms](/jwt/jws/algorithms) and [JWE algorithms](/jwt/jwe/algorithms) to choose an `alg`.
