HMAC-SHA256 or RSA for JWT Signatures?

By Michael Lip ·

Use HS256 when one service signs and verifies its own tokens, because a single shared secret and a 32-byte signature are enough. Use RS256 or ES256 when a separate service must verify tokens it does not issue, because only the holder of the private key can sign. Always fix the algorithm in the verifier.

Symmetric and asymmetric signing

HS256 is HMAC-SHA256: one secret both signs and verifies. RS256 is RSASSA-PKCS1-v1_5 with SHA-256: a private key signs and the matching public key verifies. ES256 is ECDSA over P-256 with SHA-256, and it works the same way with a much smaller key.

Signature size

The size of the third segment follows from the algorithm. An HS256 signature is the 32-byte SHA-256 digest, which is 43 base64url characters. An RS256 signature over a 2048-bit key is 256 bytes, which is 342 base64url characters. An ES256 signature is the 64-byte pair r||s, which is 86 characters. On a busy API the difference between 43 and 342 characters per request is real bandwidth, and it is one reason ES256 is popular for access tokens.

Who can mint a token

This is the decision that matters. With HS256 every party that can verify a token can also create one, because the key is the same. That is fine inside one service and dangerous across a boundary: a microservice that only needs to check tokens would receive the power to issue them. With RS256 or ES256 the verifier holds only the public key, so a compromised verifier cannot sign anything.

Key distribution and rotation

A shared HMAC secret has to reach every verifier, which is one more place it can leak, and rotating it means redeploying every service in the same window. An asymmetric setup publishes a public key, usually as a JWK set at a well-known URL, and identifies each key with a kid header. Rotation means publishing the new public key first and then switching the signer, with no verifier downtime.

Cost

HS256 is a single hash pass and costs almost nothing. RSA verification is cheap but RSA signing is slow and needs a big key. ECDSA signing is fast, but the implementation has sharp edges: the signature must be r||s and not DER inside a JWT, and a reused nonce leaks the private key. Use a maintained library and do not hand-roll the curve arithmetic.

Choose

The mistake to avoid

Do not let the token choose its own algorithm. A verifier that reads alg from the header and honours it can be switched from RS256 to HS256, and then the public key becomes the HMAC secret. Fix the accepted algorithms in the verifier, reject none, and check that the key type matches the algorithm before any crypto runs.

Test it

The JWT signature verifier checks HS256, HS384, HS512, RS256 and ES256 in your browser with WebCrypto. The HMAC generator produces a signature you can compare by hand. If a valid token is rejected, read why JWT signature verification fails.

Common questions

Can HS256 tokens be verified by a service that cannot create them?

No. HS256 uses one shared secret for signing and verification, so any service that can verify a token can also mint one. Use RS256 or ES256 when a verifier must not be able to sign.

Why is an RS256 token longer than an HS256 token?

The signature segment follows the key size. HS256 produces a 32-byte digest, RS256 over a 2048-bit key produces 256 bytes, and ES256 produces 64 bytes.

What happens if a verifier trusts the alg header?

An attacker can switch an RS256 token to HS256 and use the public key as the HMAC secret, which the verifier will accept. Fix the allowed algorithms in the verifier and reject none.

Scan your system prompt with LochBot — free, client-side, no data sent anywhere.

By the same builder: GitHub — theluckystrike BeLikeNative — Grammar AI EarlyThunder — Dev Blog Bug Bounty Reality Zovo — AI Dev Tools