The one-sentence difference
HS256 signs with a secret and verifies with the same secret. RS256 signs with a private key and verifies with the matching public key. Everything else follows from that.
What HS256 actually is
HS256 is HMAC using SHA-256. Take the token's header.payload bytes, take a secret, run them through HMAC, and the 32-byte output is the signature. To verify, do the same thing and compare.
The secret is a shared secret. Whoever verifies must hold it, and holding it means they can also sign. There is no way to hand out "verify only" access.
The secret must also be long. RFC 7518 says at least 256 bits for HS256, which means 32 random bytes. A short or human-memorable secret can be brute-forced offline by anyone holding a single valid token, since they can test guesses as fast as their hardware allows with no rate limit. The encoder will sign with any string you give it, which is useful for experiments and a bad idea in production.
What RS256 actually is
RS256 is RSASSA-PKCS1-v1_5 with SHA-256. The issuer holds an RSA private key, usually 2048 bits or more. Signing produces a 256-byte signature for a 2048-bit key. Anyone with the public key can check it, and the public key gives them no ability to sign.
That split is the point. An identity provider signs tokens; a dozen microservices verify them. Each service fetches the public key, typically from a JWKS endpoint at /.well-known/jwks.json, caches it, and never sees the private key at all. If one service is compromised, the attacker gets a public key and nothing else.
Side by side
| HS256 | RS256 | |
|---|---|---|
| Key material | One shared secret | Private key to sign, public key to verify |
| Who can forge tokens | Anyone who can verify | Only the private key holder |
| Signature size | 32 bytes (43 chars base64url) | 256 bytes for RSA-2048 (342 chars) |
| Signing speed | Microseconds | Milliseconds |
| Verification speed | Microseconds | Fast, though slower than HMAC |
| Key rotation | Redistribute the secret to every verifier | Publish a new public key; verifiers pick it up by kid |
| Good fit | One service issues and verifies its own tokens | Any setup with separate issuer and verifiers |
Where each one goes wrong
HS256 with a weak secret. Covered above. Tools like hashcat have a JWT mode for exactly this. If the secret is a dictionary word, it is already broken.
HS256 across trust boundaries. Sharing the secret with a partner or a third-party service means trusting them not to mint tokens. Once shared, you also cannot rotate it without coordinating with everyone.
RS256 with algorithm confusion. This is the famous one. A verifier that trusts the token's alg header and uses one key variable for everything can be tricked: the attacker takes the public key, which is public, signs a forged token with HS256 using that PEM string as the HMAC secret, and sets alg: HS256. A naive verifier runs HMAC with the public key as the secret and it matches. The fix is to pin the expected algorithm in the verifier and never let the token choose. Every mainstream library now requires an explicit algorithms list for this reason, and this site's decoder verifies with the algorithm you select, not the one in the header.
RS256 with unverified JWKS. Fetching keys over plain HTTP, or from a URL taken from the token's own header (jku), lets an attacker supply their own key. Fetch JWKS over TLS from a URL you configured, never one the token tells you.
Token size matters more than you think
An RS256 token is around 300 characters longer than the same HS256 token. In a cookie sent on every request, or a header on every API call, that adds up. If you need asymmetric verification and size is a concern, ES256 gives you a 64-byte signature with the same public/private split. EdDSA (Ed25519) is smaller and faster still, though library support is less universal.
A decision rule
Use HS256 when a single service issues and verifies its own tokens, nobody else ever needs to check them, and you can generate a 32-byte random secret and keep it in a secrets manager.
Use RS256 (or ES256) the moment a second party needs to verify. That includes a separate API behind an identity provider, a mobile app, or any third party. Publish the public key through JWKS with a kid, and rotate by adding a new key before retiring the old one.
If you are choosing today for a new system and your libraries support it, ES256 is the better asymmetric default. If you are integrating with Auth0, Cognito, Firebase, or most commercial identity providers, RS256 is what they issue, and you verify what you are given.
You can see the difference for yourself: build a token in the encoder with HS256, switch the algorithm to RS256, and watch the signature triple in length.