RS256 vs HS256: A deep dive into JWT signing algorithms
Symmetric vs asymmetric JWT signatures: how each algorithm works, when to use which, and the security tradeoffs every developer should know
Choosing the right signing algorithm for your JSON Web Tokens is one of those decisions that seems minor at first but has serious consequences for your system's security model, key management overhead, and architectural flexibility. This article breaks down the two most common options, RS256 and HS256, from the ground up.
What signing actually does
A JWT consists of three Base64url-encoded segments: a header, a payload, and a signature. The signature exists to guarantee integrity and authenticity. When a service receives a token, the signature lets it answer two questions: has this token been tampered with, and was it issued by a party I trust?
Both RS256 and HS256 answer those questions, but they rely on fundamentally different cryptographic primitives to do it.
HS256: Symmetric signing with HMAC-SHA256
HS256 uses HMAC-SHA256, a symmetric algorithm. "Symmetric" means the same secret key is used to both create and verify the signature.
The process works like this:
- Concatenate the Base64url-encoded header and payload with a period separator.
- Feed that string and the shared secret into the HMAC-SHA256 function.
- The output is the signature.
Verification is identical. The verifier takes the header and payload, runs the same HMAC-SHA256 computation with the same secret, and checks whether the result matches the signature in the token.
The core property here is that anyone who can verify a token can also create one. There is no distinction between "signer" and "verifier" because both operations require the same secret. This has profound implications for multi-service architectures.
The cryptographic internals
HMAC-SHA256 works by running SHA-256 twice with derived keys. Given a secret key K, it computes:
Where K' is the key padded or hashed to the block size (64 bytes for SHA-256), opad is 0x5c repeated, and ipad is 0x36 repeated. The nested hashing prevents length extension attacks that would be possible with a naive SHA256(key || message) construction.
HMAC has a well-studied security proof: if the underlying hash function is a pseudorandom function, then HMAC is a secure MAC. In practice, HS256 signatures are fast to compute and compact (32 bytes before encoding).
RS256: Asymmetric signing with RSA-SHA256
RS256 uses RSASSA-PKCS1-v1_5 with SHA-256, an asymmetric algorithm. The signer holds a private key, and verifiers only need the corresponding public key.
The signing process:
- Concatenate the Base64url-encoded header and payload.
- Hash the result with SHA-256.
- Apply the RSA signing operation using the private key. This involves padding the hash with the PKCS#1 v1.5 signature scheme, then performing modular exponentiation with the private exponent.
Verification uses the public key:
- Apply the RSA verification operation (modular exponentiation with the public exponent) to the signature.
- Strip and validate the PKCS#1 v1.5 padding.
- Compare the extracted hash with a freshly computed SHA-256 hash of the header and payload.
The critical difference is directional trust. Only the holder of the private key can create valid signatures, but anyone with the public key can verify them. The public key can be distributed freely without compromising signing authority.
RSA key sizes and security margins
The security of RS256 depends on the difficulty of factoring large integers. A 2048-bit RSA key provides roughly 112 bits of security, which is considered sufficient through 2030 by most standards bodies. A 4096-bit key provides approximately 140 bits and is recommended for systems with longer key lifetimes.
The tradeoff is performance. RSA signing with a 2048-bit key is roughly 10 to 50 times slower than HS256, depending on the implementation and hardware. Verification is faster than signing (because the public exponent is typically small, often 65537), but still slower than HMAC.
Where the choice actually matters
Single-service applications
If you have one monolithic application that both issues and validates tokens, HS256 is the simpler choice. There is one secret, one service, and no need for key pair management. The performance advantage of HMAC is a bonus, though rarely the deciding factor at typical request volumes.
Microservice and distributed architectures
This is where RS256 becomes essential. Consider a common pattern: an authentication service issues JWTs, and dozens of downstream services validate them to authorize requests.
With HS256, every downstream service needs the shared secret. Each service that holds the secret is a potential point of compromise, and any compromised service can forge tokens for the entire system. You have effectively turned every microservice into a fully trusted identity provider.
With RS256, only the auth service holds the private key. Downstream services get the public key, usually fetched dynamically from a JWKS (JSON Web Key Set) endpoint. A compromised downstream service can read tokens but cannot forge new ones. The blast radius of a breach is dramatically smaller.
Third-party consumers
If external clients or partners need to verify your tokens, RS256 is the only practical option. You publish your public key (or JWKS endpoint), and third parties can verify tokens without you sharing any sensitive material. HS256 would require sharing your signing secret with external parties, which is a non-starter from a security perspective.
JWKS and key rotation
RS256 works naturally with the JWKS standard (RFC 7517). An authorization server publishes its public keys at a well-known URL, typically /.well-known/jwks.json. Each key in the set has a key ID (kid), which is also included in the JWT header. Verifiers fetch the key set, find the key matching the kid, and use it for verification.
This enables seamless key rotation. The auth server can start signing with a new key pair while keeping the old public key in the JWKS for a transition period. Verifiers automatically pick up the new key. Once all outstanding tokens signed with the old key have expired, the old public key is removed from the JWKS.
Key rotation with HS256 is considerably more painful. You need to distribute a new secret to every service simultaneously, or maintain logic to try multiple secrets during a transition window. In large systems, coordinating this is operationally complex.
Security considerations
Algorithm confusion attacks
One of the most infamous JWT vulnerabilities involves algorithm confusion. If a server's verification logic accepts the algorithm specified in the JWT header without restriction, an attacker can take a public RS256 key, create a token signed with HS256 using that public key as the HMAC secret, and the server may verify it successfully.
This works because the server sees "alg": "HS256" in the header, treats the RSA public key as an HMAC secret, and the signature checks out. The attacker never needed the private key.
The fix is straightforward: never let the token's header dictate which algorithm to use for verification. Your verification code should explicitly specify the expected algorithm. Most modern JWT libraries support this, but older or misconfigured implementations remain vulnerable.
Key management risks
HS256's risk is secret exposure. If the secret leaks, an attacker can forge any token, and you have no way to distinguish forged tokens from legitimate ones until you rotate the secret and invalidate all existing tokens.
RS256's risk is private key exposure. If the private key leaks, an attacker can forge tokens, but you can rotate keys and revoke the compromised key in your JWKS. Public key exposure is not a concern by design.
Brute force resistance
HS256 secrets must be long and random. A short or predictable secret is vulnerable to offline brute force attacks, since the attacker has the token, the algorithm, and all the inputs except the secret. NIST recommends secrets at least as long as the hash output (256 bits for HS256). In practice, use a cryptographically random string of at least 32 bytes.
RS256 keys are generated by standard RSA key generation and are not susceptible to the same class of brute force attacks, provided the key size is adequate.
Performance benchmarks in context
Raw throughput numbers vary by language, library, and hardware, but the relative picture is consistent:
HS256 signing is typically on the order of tens of microseconds. RS256 signing is on the order of single-digit milliseconds with a 2048-bit key. RS256 verification is faster than signing but still slower than HS256 verification.
For the vast majority of applications, this difference is irrelevant. If your service handles 10,000 requests per second, the cumulative cost of RS256 verification is a few seconds of CPU time, which is negligible compared to database queries, network I/O, and business logic. Performance should only drive this decision in extreme edge cases like embedded systems or very high throughput gateways with tight latency budgets.
A note on ES256
It is worth mentioning ES256 (ECDSA with P-256 and SHA-256) as a modern asymmetric alternative. ES256 provides the same signer/verifier separation as RS256 but with significantly smaller keys (256-bit vs 2048-bit) and faster operations. The signatures are also more compact (64 bytes vs 256 bytes for RSA-2048).
ES256 is increasingly the default recommendation for new systems that need asymmetric signing. RS256 remains widely used due to ecosystem maturity and library support, but if you are starting fresh, ES256 is worth serious consideration.
Practical recommendations
Use HS256 when your signing and verification happen in the same trust boundary, typically a single application or a small cluster of services that already share secrets through a secure channel. Keep the secret long, random, and stored in a secrets manager.
Use RS256 (or ES256) when tokens cross trust boundaries, when multiple independent services verify tokens, when third parties need to verify your tokens, or when you need robust key rotation via JWKS. Store the private key in an HSM or a managed key service, and publish the public key through a JWKS endpoint.
Whatever you choose, explicitly pin the algorithm in your verification logic, rotate keys on a regular schedule, and treat token signing keys with the same care you give to any other critical secret in your infrastructure.