HS256 and RS256 confusion attacks in real systems
How algorithm confusion appears when verifiers mix symmetric and asymmetric assumptions, with concrete testing and mitigation notes.
Algorithm confusion is one of those JWT issues that sounds too specific until you find a service with three authentication libraries and a shared helper named verifyToken. The classic attack abuses a verifier that accepts both HS256 and RS256 while using the same key material in the wrong role. If the server treats an RSA public key as an HMAC secret, an attacker can sign a forged HS256 token with data that was never meant to be secret.
Why the bug survives reviews
Code review often sees a call like jwt.verify(token, key) and moves on. The dangerous decision is hidden in defaults: which algorithms are accepted, how the key is selected, and whether the code branches by the token header. Teams also copy snippets between services. A public key string that was safe in an RS256 verifier becomes dangerous when passed to a verifier that trusts the incoming alg value.
Attackers look for exposed JWKS endpoints, PEM files in mobile apps, frontend bundles, documentation examples, and error messages that leak verifier behavior. Once a public key is available, they create a token with alg set to HS256, sign the payload using that public key bytes as the HMAC secret, and test whether the service accepts it. The payload change is usually small: elevate scope, switch tenant_id, or set admin to true.
Testing without causing noise
Use a low privilege account and target a non-destructive endpoint first, such as profile metadata or a feature flag route. Keep iss, aud, and exp valid so a rejection points more clearly at signature handling. If the service returns invalid algorithm, that is a good sign. If it returns invalid signature, confirm whether the algorithm is pinned or whether your key formatting is wrong. PEM headers, newlines, and trailing spaces can change the result.
Defender checklist
Pin algorithms per issuer. Do not let the token header decide whether a key is symmetric or asymmetric. Store HMAC secrets and RSA or EC public keys in different configuration paths, with different types if the language supports it. A verifier for RS256 should refuse to start if configured with an HMAC secret, and a verifier for HS256 should never load a public key from JWKS.
The operational lesson
The fix is boring separation. One issuer, one expected algorithm family, one key source, one verification wrapper. If a migration from HS256 to RS256 is in progress, do not accept both algorithms indefinitely on the same audience. Create a short overlap window, monitor old algorithm usage, and delete the fallback when the window closes.