Skip to content

JWT signing algorithms compared

· 8 min read

The alg header of a JWT is three or five characters long and decides more about your security posture than anything else in the token. It determines who can verify, who can forge, how big the token gets, and what happens when you need to rotate keys. This is a practical comparison of the algorithms you will actually encounter.

The fundamental split

Every JWT algorithm is either symmetric or asymmetric, and that single distinction decides most of the design.

Symmetric (HS*) uses one shared secret for both signing and verifying. Simple, fast, and self-defeating the moment more than one party needs to verify — because the ability to verify is the ability to forge. There is no read-only version of a shared secret.

Asymmetric (RS*, PS*, ES*, EdDSA) splits the key in two. The issuer signs with a private key it never shares; anyone verifies with the corresponding public key, which is safe to publish. Your API gateway and a dozen services can all validate tokens while none of them can mint one.

The algorithms

algMechanismSignature size
HS256HMAC with SHA-256, shared secret32 bytes
RS256RSA PKCS#1 v1.5 with SHA-256256 bytes (2048-bit key)
PS256RSA-PSS with SHA-256256 bytes (2048-bit key)
ES256ECDSA on P-256 with SHA-25664 bytes
EdDSAEd2551964 bytes

The 384 and 512 variants of each are the same construction with a larger SHA-2 hash. They are rarely the right answer on their own — SHA-256 is not the weak link in any of these, and the larger hashes mostly cost you bytes.

HS256

The default in most tutorials, and correct in exactly one situation: the issuer and the verifier are the same trust boundary. A monolith that signs its own session tokens and checks them on the next request has nothing to gain from asymmetric keys.

The failure mode is organisational rather than cryptographic. The secret gets shared with a second service, then a third, then a partner who "just needs to validate tokens" — and now four parties can impersonate any user. Its other weakness is key quality: an HMAC secret is just a string, so someone eventually uses secret or a short passphrase, and the signature becomes brute-forceable offline. If you use HS256, generate a long random secret with a cryptographic generator and treat it like a private key.

RS256

The workhorse of the identity industry. Auth0, Okta, Azure AD, Google — the tokens you consume from a managed identity provider are almost always RS256. Universally supported by every library and runtime, and the JWKS ecosystem is built around it, so key rotation is a solved problem: the issuer publishes its public keys at a well-known URL, the token's kid selects one, and rotation needs no coordination with verifiers.

The cost is size. A 2048-bit RSA signature is 256 bytes, about 342 characters after base64url encoding. On a token sent with every request, in a header, that adds up — and some proxies have surprisingly modest header size limits.

PS256

RSA-PSS: the same RSA keys, a better padding scheme with a randomised salt. Cryptographers consider it the more modern choice, and the standards world has been nudging toward it for years. Same signature size as RS256, slightly less universal library support. If you are already on RSA and your stack supports it, it is a free improvement; it is rarely worth a migration on its own.

ES256

Elliptic curve signing on P-256. The practical advantage is compactness: a 64-byte signature against RSA's 256, for security considered comparable to a 3072-bit RSA key. Keys are small too, which matters for embedded and mobile clients.

The historic caveat is that ECDSA requires a unique random nonce per signature, and reusing one leaks the private key — the flaw behind the 2010 PlayStation 3 key recovery. This is the signer's problem, not the verifier's, and any current library handles it correctly (many now use deterministic nonces per RFC 6979). It is a reason to use a well-maintained library, not a reason to avoid ES256.

EdDSA

Ed25519 is the modern default in most new cryptographic designs: fast, compact, deterministic by construction, and designed to be hard to implement badly. Support in the JWT world is good but not universal — notably, browser WebCrypto support arrived late, so client-side verification is less portable than for the others. Excellent for a system where you control both ends.

Choosing

  • One service signs and verifies its own tokens → HS256, with a long random secret.
  • Multiple services verify tokens from one issuer → RS256, or ES256 if you care about size and control the libraries.
  • You consume tokens from an identity provider → whatever they issue, almost always RS256. Fetch their JWKS, do not hardcode a key.
  • Bandwidth or header size is tight → ES256.
  • Greenfield, both ends yours → EdDSA is defensible; check every runtime in the path first.

The one rule that outranks the choice

Whichever algorithm you pick, the verifier decides which algorithms it accepts — never the token. A library that reads alg from the header and obeys it is vulnerable to the none algorithm and to RS256→HS256 confusion, where an attacker signs a tampered token using your public key as an HMAC secret. Pass an explicit allow-list. Both attacks are covered in detail in how to verify a JWT signature.

Checking what you actually received

To see which algorithm an issuer is really using, look at the decoded header — and do it without handing a live token to a third party. KeepItLocally's JWT decoder and verifier shows the header, payload, and expiry state entirely in your browser, and verifies HS256/384/512 with a secret or RS256/384/512 and ES256/384 with a PEM public key, all through WebCrypto. No backend, no transmission — the Content-Security-Policy blocks third-party requests outright.

Quick reference

  • Symmetric means verifiers can forge — HS256 only inside one trust boundary.
  • RS256 is the interoperable default, at 256 bytes per signature.
  • ES256 gives comparable security in 64 bytes.
  • Pin the algorithm at the verifier. Always.