HMAC-SHA256 or RSA for JWT Signatures?
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
- One service signs and verifies — HS256 with a random secret of at least 32 bytes. Simple, fast, no key distribution.
- Several services verify, one issues — RS256 or ES256. Publish the public key as a JWK set and rotate with
kid. - Browser or mobile client verifies — an asymmetric algorithm only. A client cannot keep a shared secret, so HS256 there is a public key with extra steps.
- Long-lived tokens or strict compliance — RS256 with at least a 2048-bit key, or ES256 with P-256.
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.