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 a CryptoKey (or pair, or raw bytes). Use when you need a non-extractable CryptoKey or 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.

examples.ts
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

OptionApplies toDefault
kidAnycrypto.randomUUID()
useAny
extractableAnytrue
keyUsageAnyDerived from alg
modulusLengthRSA only2048
publicExponentRSA only0x010001 (65537)
namedCurveEC / OKPDerived from alg (see below)
Any JWK metadataAnyMerged 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 algorithms (PBES2-*) aren't generation-time algorithms — they're key-derivation algorithms. Passing "PBES2-HS256+A128KW" to generateJWK() throws. Use deriveJWKFromPassword instead.

Algorithm → output shape

AlgorithmReturns
HS*, A*KW, A*GCM, A*GCMKWJWK_oct<Alg>
A128CBC-HS256, A192CBC-HS384, A256CBC-HS512JWK_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:

AlgorithmDefault curveAllowed curves
ES256P-256P-256 only
ES384P-384P-384 only
ES512P-521P-521 only
Ed25519/EdDSAEd25519Ed25519, Ed448 (experimental)
ECDH-ES*P-256P-256, P-384, P-521, X25519
For new ECDH-ES key pairs, 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 familytoJWK: false (default)toJWK: true
HS*, A*KW, A*GCM, A*GCMKWCryptoKeyJWK_oct
A128CBC-HS256, A192CBC-HS384, A256CBC-HS512Uint8Array<ArrayBuffer> (composite AES+HMAC bytes)JWK_oct
RS*, PS*, RSA-OAEP*CryptoKeyPair{ privateKey: JWK, publicKey: JWK }
ES*, Ed25519, EdDSA, ECDH-ES*CryptoKeyPairSame 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 a CryptoKey directly usable as a CEK for alg: "dir".
  • Composite AES+HMAC bytesgenerateKey("A256CBC-HS512") gives you the 64-byte composite buffer ready for use with encrypt() (alg: "dir").
non-extractable.ts
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