Algorithms
JWS algorithms
The alg header parameter on a JWS names the signing algorithm. unjwt supports the full RFC 7518 registry (minus none, which is unsafe by design) plus Ed25519 / EdDSA from RFC 8037.
Registry
| Family | Identifiers | Key type | Notes |
|---|---|---|---|
| HMAC | HS256, HS384, HS512 | Symmetric (kty: "oct") | Fastest, simplest. Same key signs and verifies. |
| RSA PKCS#1 v1.5 | RS256, RS384, RS512 | RSA keypair (kty: "RSA") | Widely supported but deterministic. Prefer RSA-PSS for new keys. |
| RSA-PSS | PS256, PS384, PS512 | RSA keypair (kty: "RSA") | Probabilistic — modern RSA signature scheme. |
| ECDSA | ES256, ES384, ES512 | EC keypair (kty: "EC") | Much smaller keys than RSA for equivalent security. |
| EdDSA | Ed25519, EdDSA | OKP keypair (kty: "OKP") | Modern, fast, constant-time. Ed25519 is the concrete scheme. |
HS256 are one name for one specific algorithm: HMAC with SHA-256. The suffix number is the hash size (256, 384, 512). Larger isn't always better — SHA-256 is plenty for most purposes, and it's faster.Choosing
Building an API with self-issued tokens — use HS256. Simple, fast, one secret to manage. This is the default choice for 80% of apps.
Third parties should verify without being able to sign — use an asymmetric scheme. In order of preference for new systems:
Ed25519 — modern, small keys, constant-time, no parameter choices to get wrong.
ES256 — widely supported, small signatures, safe defaults.
PS256 — when RSA is mandated (regulated environments, legacy compatibility).
RS256 — when a counterparty requires PKCS#1 v1.5 specifically.
Key rotation across a fleet of services — RS256 or PS256 with a published JWKS endpoint. Verifiers cache the JWKS; you rotate by publishing a new kid.
Post-quantum migration — no JWS algorithm is post-quantum secure today. Hybrid signing (sign with both classical and PQC in a multi-signature envelope) is the migration path — see Multi-signature.
Performance at a glance
Rough relative costs per signature on a typical server (ordered fastest → slowest):
HS256 — single HMAC pass. Microseconds.
Ed25519 — constant-time EC operation. Low microseconds.
ES256 — ECDSA. Low microseconds.
PS256 / RS256 — RSA signing dominates the cost. ~100× slower than HS256.
ES512 / RS512 / PS512 — largest hash variant of each family.
Verification is typically cheaper than signing for asymmetric schemes, but the exact ratio varies.
Generating a key per algorithm
All the examples below use generateJWK, which returns a key with alg baked in — so sign()/verify() don't need options.alg.
const key = await generateJWK("HS256");
// { kty: "oct", k: "...", alg: "HS256", kid: "..." }
const { privateKey, publicKey } = await generateJWK("RS256", { modulusLength: 2048 });
// privateKey: JWK_RSA_Private ({ kty: "RSA", d, n, e, p, q, dp, dq, qi, alg, kid })
// publicKey: JWK_RSA_Public ({ kty: "RSA", n, e, alg, kid })
const { privateKey, publicKey } = await generateJWK("Ed25519");
// kty: "OKP", crv: "Ed25519", alg: "Ed25519"
const { privateKey, publicKey } = await generateJWK("ES256");
// kty: "EC", crv: "P-256", alg: "ES256"
Curves
ES* uses NIST curves:
ES256→P-256ES384→P-384ES512→P-521(note: 521, not 512 — the curve name isP-521in JOSE)
EdDSA uses Edwards curves:
Ed25519— the common case. Recommended.Ed448— larger, slower, and currently experimental in Node.js (emits anExperimentalWarning).
Which identifier goes in the header?
sign() always writes the signer's algorithm into the protected header as alg. verify() reads it back and checks it against the allowlist (algorithms option, or inferred from the key).
Never attempt to infer the algorithm yourself by parsing the header — that's the attack vector behind the "alg: none" family of JWT CVEs. Let unjwt do the match.