Generating keys
unjwt gives you two functions for creating keys:
generateJWK— returns a JWK (or a pair of JWKs). Best for 99% of cases.generateKey— returns aCryptoKey(or pair, or raw bytes). Use when you need a non-extractableCryptoKeyor raw byte output.
Both are in unjwt/jwk.
generateJWK
generateJWK(alg, options?)
Always returns a JWK (or pair) with alg and kid set. The kid is a fresh crypto.randomUUID() unless you provide one.
import { generateJWK } from "unjwt/jwk";
// Symmetric → single JWK
const hmac = await generateJWK("HS256");
// { kty: "oct", k: "...", alg: "HS256", kid: "4c3d-..." }
// Asymmetric → keypair
const { privateKey, publicKey } = await generateJWK("RS256");
// Custom kid
const ec = await generateJWK("ES256", { kid: "ec-signing-2025" });
// Custom RSA modulus
const rsa = await generateJWK("RS256", { modulusLength: 4096, use: "sig" });
// Custom EC curve
const ecdh = await generateJWK("ECDH-ES+A256KW", { namedCurve: "X25519" });
// EdDSA explicit
const ed = await generateJWK("Ed25519");
Options
| Option | Applies to | Default |
|---|---|---|
kid | Any | crypto.randomUUID() |
use | Any | — |
extractable | Any | true |
keyUsage | Any | Derived from alg |
modulusLength | RSA only | 2048 |
publicExponent | RSA only | 0x010001 (65537) |
namedCurve | EC / OKP | Derived from alg (see below) |
| Any JWK metadata | Any | Merged into the result (x5c, etc.) |
alg, kty, key_ops, and ext are managed by the library and cannot be overridden via options — they must match the key material.
PBES2-*) aren't generation-time algorithms — they're key-derivation algorithms. Passing "PBES2-HS256+A128KW" to generateJWK() throws. Use deriveJWKFromPassword instead.Algorithm → output shape
| Algorithm | Returns |
|---|---|
HS*, A*KW, A*GCM, A*GCMKW | JWK_oct<Alg> |
A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 | JWK_oct<Alg> (composite AES+HMAC bytes serialized as one key) |
RS*, PS*, RSA-OAEP* | { privateKey: JWK_RSA_Private<Alg>; publicKey: JWK_RSA_Public<Alg> } |
ES* | { privateKey: JWK_EC_Private<Alg>; publicKey: JWK_EC_Public<Alg> } |
Ed25519, EdDSA | { privateKey: JWK_OKP_Private<Alg>; publicKey: JWK_OKP_Public<Alg> } |
ECDH-ES* | EC pair (default curves) or OKP pair (for X25519) — see JWK_Pair |
The literal algorithm is preserved in the return type — generateJWK("HS256") is JWK_oct<"HS256">, generateJWK("RS256") is { publicKey: JWK_RSA_Public<"RS256">; privateKey: JWK_RSA_Private<"RS256"> }. This narrowing flows through into sign / verify / encrypt / decrypt, which reject JWKs whose alg points at the wrong algorithm family at the type level. For generic variables, JWK_Pair<Alg> picks the right pair shape per algorithm (e.g. JWK_Pair<"ECDH-ES+A256KW"> is an EC pair or an OKP pair — whichever the runtime curve produces).
Curve defaults
For EC/OKP algorithms, the namedCurve defaults follow the JOSE registry:
| Algorithm | Default curve | Allowed curves |
|---|---|---|
ES256 | P-256 | P-256 only |
ES384 | P-384 | P-384 only |
ES512 | P-521 | P-521 only |
Ed25519/EdDSA | Ed25519 | Ed25519, Ed448 (experimental) |
ECDH-ES* | P-256 | P-256, P-384, P-521, X25519 |
X25519 is the modern choice — constant-time, compact, and not subject to the NIST-curve "is this a valid point?" validation overhead.generateKey
generateKey(alg, options?)
Returns a CryptoKey, CryptoKeyPair, or Uint8Array depending on the algorithm — without wrapping it as a JWK. The exact return type is narrowed by alg and by options.toJWK:
| Algorithm family | toJWK: false (default) | toJWK: true |
|---|---|---|
HS*, A*KW, A*GCM, A*GCMKW | CryptoKey | JWK_oct |
A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 | Uint8Array<ArrayBuffer> (composite AES+HMAC bytes) | JWK_oct |
RS*, PS*, RSA-OAEP* | CryptoKeyPair | { privateKey: JWK, publicKey: JWK } |
ES*, Ed25519, EdDSA, ECDH-ES* | CryptoKeyPair | Same pair shape |
When to use generateKey
- Non-extractable keys — call
generateKey("HS256", { extractable: false })to keep the key material inside Web Crypto only. Handy for server-side session-signing keys that shouldn't be exported. - Raw bytes for
dir/ AES-CBC-HS* —generateKey("A256GCM")returns aCryptoKeydirectly usable as a CEK foralg: "dir". - Composite AES+HMAC bytes —
generateKey("A256CBC-HS512")gives you the 64-byte composite buffer ready for use withencrypt()(alg: "dir").
import { generateKey } from "unjwt/jwk";
// A CryptoKey that can never be exported as JWK
const sessionKey = await generateKey("HS256", { extractable: false });
// sign() can still use it
await sign({ sub: "u1" }, sessionKey, { alg: "HS256" });
toJWK: true vs generateJWK
generateKey("HS256", { toJWK: true }) and generateJWK("HS256") both return a JWK_oct, but generateJWK is the ergonomic path for JWK output: it always sets kid and accepts JWK metadata fields in its options. Use generateKey({ toJWK: true }) only when you need a specific non-extractable setup that happens to serialize as JWK — rare.
See also
- Importing & exporting →
- JWS algorithms → — which signing alg to pick.
- JWE algorithms → — which encryption alg to pick.
- Password derivation → — for PBES2.