# JWS algorithms

The `alg` header parameter on a JWS names the signing algorithm. unjwt supports the full [RFC 7518](https://www.rfc-editor.org/rfc/rfc7518) registry (minus `none`, which is unsafe by design) plus `Ed25519` / `EdDSA` from [RFC 8037](https://www.rfc-editor.org/rfc/rfc8037).

## Registry

<table>
<thead>
  <tr>
    <th>
      Family
    </th>
    
    <th>
      Identifiers
    </th>
    
    <th>
      Key type
    </th>
    
    <th>
      Notes
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      HMAC
    </td>
    
    <td>
      <code>
        HS256
      </code>
      
      , <code>
        HS384
      </code>
      
      , <code>
        HS512
      </code>
    </td>
    
    <td>
      Symmetric (<code>
        kty: "oct"
      </code>
      
      )
    </td>
    
    <td>
      Fastest, simplest. Same key signs and verifies.
    </td>
  </tr>
  
  <tr>
    <td>
      RSA PKCS#1 v1.5
    </td>
    
    <td>
      <code>
        RS256
      </code>
      
      , <code>
        RS384
      </code>
      
      , <code>
        RS512
      </code>
    </td>
    
    <td>
      RSA keypair (<code>
        kty: "RSA"
      </code>
      
      )
    </td>
    
    <td>
      Widely supported but deterministic. Prefer RSA-PSS for new keys.
    </td>
  </tr>
  
  <tr>
    <td>
      RSA-PSS
    </td>
    
    <td>
      <code>
        PS256
      </code>
      
      , <code>
        PS384
      </code>
      
      , <code>
        PS512
      </code>
    </td>
    
    <td>
      RSA keypair (<code>
        kty: "RSA"
      </code>
      
      )
    </td>
    
    <td>
      Probabilistic — modern RSA signature scheme.
    </td>
  </tr>
  
  <tr>
    <td>
      ECDSA
    </td>
    
    <td>
      <code>
        ES256
      </code>
      
      , <code>
        ES384
      </code>
      
      , <code>
        ES512
      </code>
    </td>
    
    <td>
      EC keypair (<code>
        kty: "EC"
      </code>
      
      )
    </td>
    
    <td>
      Much smaller keys than RSA for equivalent security.
    </td>
  </tr>
  
  <tr>
    <td>
      EdDSA
    </td>
    
    <td>
      <code>
        Ed25519
      </code>
      
      , <code>
        EdDSA
      </code>
    </td>
    
    <td>
      OKP keypair (<code>
        kty: "OKP"
      </code>
      
      )
    </td>
    
    <td>
      Modern, fast, constant-time. <code>
        Ed25519
      </code>
      
       is the concrete scheme.
    </td>
  </tr>
</tbody>
</table>

<note>

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.

</note>

## 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:

<steps level="4">

#### `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.

</steps>

**Key rotation across a fleet of services** — `RS256` or `PS256` with a published [JWKS endpoint](/examples/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](/jwt/jws/multi-signature).

## Performance at a glance

Rough relative costs per signature on a typical server (ordered fastest → slowest):

<steps level="4">

#### `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.

</steps>

Verification is typically cheaper than signing for asymmetric schemes, but the exact ratio varies.

<tip>

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.

</tip>

## Generating a key per algorithm

All the examples below use [`generateJWK`](/jwk/generating), which returns a key with `alg` baked in — so `sign()`/`verify()` don't need `options.alg`.

<CodeGroup>

```ts [hs256.ts]
const key = await generateJWK("HS256");
// { kty: "oct", k: "...", alg: "HS256", kid: "..." }
```

```ts [rs256.ts]
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 })
```

```ts [ed25519.ts]
const { privateKey, publicKey } = await generateJWK("Ed25519");
// kty: "OKP", crv: "Ed25519", alg: "Ed25519"
```

```ts [es256.ts]
const { privateKey, publicKey } = await generateJWK("ES256");
// kty: "EC", crv: "P-256", alg: "ES256"
```

</CodeGroup>

### Curves

`ES*` uses NIST curves:

- `ES256` → `P-256`
- `ES384` → `P-384`
- `ES512` → `P-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`](/jwt/jws/verifying#algorithm-allowlist) 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.
