---
title: "JWT Algorithm Confusion Explained: Forging Tokens with RS256 to HS256"
description: "How JWT algorithm confusion works, why switching RS256 to HS256 lets an attacker sign their own tokens with a public key, and how to shut it down in your own code."
date: 2026-09-05
authors: "javelin"
tags: ["jwt", "web", "authentication", "algorithm-confusion", "rs256", "hs256", "appsec"]
category: "Engineering"
source: "/blog/jwt-algorithm-confusion-explained"
---

# JWT Algorithm Confusion Explained: Forging Tokens with RS256 to HS256

JSON Web Tokens are everywhere in modern authentication, and algorithm confusion is one of the
few JWT bugs that turns a read-only foothold into full account takeover. The attack is simple
once you see it: the token tells the server how to verify the token. If the server believes it,
an attacker can pick an algorithm the server never intended to accept.

## What is JWT algorithm confusion?

Algorithm confusion is an attack where a signed JSON Web Token is modified so its `alg` header
names a different signing algorithm than the server intended, and the server verifies it anyway
using the wrong key. In the common case an attacker changes `RS256` to `HS256`, then signs the
forged token with the server's own public key. Because the server passes that same public key
into an HMAC verification routine, the signature checks out and the attacker can set any claim
they like, including an administrator role. The bug is not in the cryptography. It is in
letting untrusted input choose which verification path runs.

## Why RS256 and HS256 make this possible

The two algorithms have fundamentally different key models, and that mismatch is the whole bug.

| | RS256 | HS256 |
|---|---|---|
| Type | Asymmetric | Symmetric |
| Signs with | Private key | Shared secret |
| Verifies with | Public key | The same shared secret |
| Key is secret? | Only the private key | Yes, always |

Under RS256 the public key is meant to be public. Publishing it is the entire point, since
anyone should be able to verify a token without being able to mint one. Under HS256 the
verification key and the signing key are the same value, so anything that can verify can also
sign.

Now consider a library call like `verify(token, key)` where `key` holds the RSA public key and
the algorithm is read from the token itself. Send a token with `"alg": "HS256"` and the library
treats that public key as an HMAC secret. The attacker knows the public key. So the attacker
can produce a valid signature.

> The dangerous ingredient is trusting the `alg` header. A verifier that accepts whatever
> algorithm the token names is deciding security policy based on attacker-controlled input.

## The attack, step by step

### 1. Obtain the public key

Sometimes the key is simply published, at `/jwks.json`, `/.well-known/jwks.json`, or in API
documentation. When it is not exposed directly, it can often be recovered from two tokens
signed by the same private key, which is enough to solve for the RSA modulus.

### 2. Change the algorithm and the claims

Decode the token, set the header to `{"alg": "HS256", "typ": "JWT"}`, and edit the payload to
claim whatever the application checks, typically something like `"role": "admin"` or
`"isAdmin": true`.

### 3. Sign with the public key as the HMAC secret

This is the step that trips people up. The HMAC secret must be the exact byte sequence the
server passes to its verify call, usually the PEM-encoded public key including its header and
footer lines and its trailing newline. A single byte of difference produces a different
signature and the forgery fails, which is why this attack is often abandoned one step before it
would
