Skip to content
Security 7 min read

Signing JWTs: HS256 vs RS256 vs ES256 — Choosing the Right Algorithm

Understand the difference between symmetric (HS256) and asymmetric (RS256, ES256) JWT signing algorithms, when to use each, and how to generate and sign JWTs in Node.js.

ToolsVito Team

Symmetric vs Asymmetric Signing

JWT signing algorithms fall into two categories:

  • Symmetric (HMAC): HS256, HS384, HS512 — the same secret key is used to both sign and verify. Simple, fast, but requires all verifiers to know the secret.
  • Asymmetric (RSA/EC): RS256, RS384, RS512, ES256, ES384, ES512 — a private key signs, a public key verifies. The public key can be shared freely; only the signer needs the private key.

HS256 — Simple Single-Service Auth

import jwt from "jsonwebtoken";

// Sign
const token = jwt.sign(
  { sub: "user123", name: "Jane Doe", iat: Math.floor(Date.now() / 1000) },
  process.env.JWT_SECRET,  // shared secret
  { algorithm: "HS256", expiresIn: "1h" }
);

// Verify
const payload = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ["HS256"] });

Use HS256 when: a single service both issues and verifies tokens — e.g., a monolithic app where the same server signs and validates sessions. Keep the secret long (>32 bytes), random, and stored in an environment variable.

Don't use HS256 when: multiple microservices need to verify tokens — you'd have to share the secret with every service, increasing the attack surface.

RS256 — Multi-Service Auth

import jwt from "jsonwebtoken";
import fs from "fs";

const privateKey = fs.readFileSync("private.pem");
const publicKey  = fs.readFileSync("public.pem");

// Sign with private key (auth service only)
const token = jwt.sign({ sub: "user123" }, privateKey, {
  algorithm: "RS256",
  expiresIn: "1h",
});

// Verify with public key (any service)
const payload = jwt.verify(token, publicKey, { algorithms: ["RS256"] });

Use RS256 when: an auth service signs tokens that multiple other services verify. Distribute the public key freely via a JWKS endpoint; protect the private key only on the auth service.

ES256 — Smaller, Faster RSA Alternative

ES256 uses ECDSA with the P-256 curve. An EC key pair is ~10× smaller than an equivalent-security RSA key, and signing is faster. ES256 produces a 64-byte signature; RS256 produces 256 bytes.

// Generate EC key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync("ec", {
  namedCurve: "P-256",
  publicKeyEncoding: { type: "spki", format: "pem" },
  privateKeyEncoding: { type: "pkcs8", format: "pem" },
});

const token = jwt.sign({ sub: "user123" }, privateKey, { algorithm: "ES256" });

JWKS — Publishing Public Keys

Auth providers publish public keys as JWKS (JSON Web Key Sets) at a well-known endpoint:

GET https://auth.example.com/.well-known/jwks.json
{
  "keys": [{
    "kty": "RSA",
    "kid": "key-2024-01",
    "use": "sig",
    "n": "...", "e": "AQAB"
  }]
}

Services fetch and cache the JWKS, then verify tokens using the key matching the kid in the JWT header. Rotate keys by publishing both old and new until all outstanding tokens expire.

Algorithm Decision Guide

  • HS256: Single service, simple setup, internal tokens
  • RS256: Distributed verification, existing RSA infrastructure
  • ES256: Distributed verification, prefer smaller tokens and faster signing
  • EdDSA (Ed25519): Emerging standard, even smaller and faster than EC — not yet in all JWT libraries

Generate and Sign JWTs

Use ToolsVito's JWT Generator to sign HS256 tokens with a custom payload and secret — useful for API testing and debugging.

Try it now — free, runs in your browser

JWT Generator

Sign HS256 JSON Web Tokens