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

FamilyIdentifiersKey typeNotes
HMACHS256, HS384, HS512Symmetric (kty: "oct")Fastest, simplest. Same key signs and verifies.
RSA PKCS#1 v1.5RS256, RS384, RS512RSA keypair (kty: "RSA")Widely supported but deterministic. Prefer RSA-PSS for new keys.
RSA-PSSPS256, PS384, PS512RSA keypair (kty: "RSA")Probabilistic — modern RSA signature scheme.
ECDSAES256, ES384, ES512EC keypair (kty: "EC")Much smaller keys than RSA for equivalent security.
EdDSAEd25519, EdDSAOKP keypair (kty: "OKP")Modern, fast, constant-time. Ed25519 is the concrete scheme.
Identifiers like 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 servicesRS256 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.

Don't optimize the algorithm choice before measuring. For the vast majority of services, any of the above is fast enough — the token-per-second budget is usually constrained by your own crypto library's scheduling, not the algorithm.

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: "..." }

Curves

ES* uses NIST curves:

  • ES256P-256
  • ES384P-384
  • ES512P-521 (note: 521, not 512 — the curve name is P-521 in JOSE)

EdDSA uses Edwards curves:

  • Ed25519 — the common case. Recommended.
  • Ed448 — larger, slower, and currently experimental in Node.js (emits an ExperimentalWarning).

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.