By AndyPublished

PS256 vs RS256: RSA-PSS or RSA-PKCS1 for JWT Signing?

PS256 and RS256 both use 2048-bit RSA keys and SHA-256, but they differ in one decisive detail: padding. RS256 uses PKCS#1 v1.5 padding (defined in PKCS#1 v1.5 / RFC 2313 and carried into RFC 8017), widely known to be deterministic and historically vulnerable to oracle attacks. PS256 uses RSA-PSS, the Probabilistic Signature Scheme defined in RFC 8017 §9.1, which has a tight security proof and is randomised. Both algorithm identifiers are registered for JWT use in RFC 7518 §3.3 and §3.5. This guide explains when the difference matters, when it doesn't, and how to migrate.

Same Key, Different Padding

A practical truth that surprises people: PS256 and RS256 can use the same RSA key pair. The algorithm identifier in the JWT header ("alg":"PS256" vs"alg":"RS256") selects the padding scheme applied during signing and verification, the underlying key material is identical. This is why most JWKS endpoints can serve one key that supports either, controlled by the alg field on the JWK.

The difference is what happens between "I have a SHA-256 hash of the signing input" and "I have an RSA signature". RS256 wraps the hash in PKCS#1 v1.5 deterministic padding before the RSA private-key operation. PS256 generates a random salt, runs the hash and salt through MGF1 (a mask generation function), and produces a randomised padding block, same RSA operation, different (and probably better) input.

Why PKCS#1 v1.5 Is Suspect

PKCS#1 v1.5 isn't "broken" for digital signatures in the sense that you can directly forge a signature without the private key. But it has a 30-year history of edge-case attacks:

  • ·Bleichenbacher's e=3 attack (2006): exploits implementations that don't fully verify the padding structure on signature verification. The classic "RSA signature forgery via a low exponent" attack.
  • ·ROBOT (2017): a Bleichenbacher-style timing oracle resurrection that affected major TLS implementations 19 years after the original was published.
  • ·Marvin attack (2023): yet another timing variant, still finding implementations that hadn't constant-timed padding parsing.

None of these directly forge a JWT signature, and a correctly-implemented RS256 verifier is safe against the known attacks. The pattern matters, though: PKCS#1 v1.5 keeps yielding new variants of old attacks because its deterministic structure leaves room for implementation mistakes. PSS is mathematically harder to misimplement in an exploitable way.

What PSS Buys You

  • ·Provable security: PSS has a tight security reduction to the RSA problem under standard assumptions. PKCS#1 v1.5 famously does not.
  • ·Randomised signatures: signing the same JWT twice produces two different signatures. RS256 produces an identical signature each time.
  • ·No "low exponent" concern: the salt and MGF1 step eliminate the attack surface that low-exponent PKCS#1 v1.5 has been bitten by.
The randomisation of PSS is irrelevant for JWT semantics, you don't gain or lose any property from signatures being non-deterministic, because the JWT payload includes iat/ jti claims that already make repeated signings produce different tokens. The benefit is purely cryptographic.

Interoperability: Where PS256 Wins, Where RS256 Wins

RS256 wins on legacy reach

Every JWT library ever shipped supports RS256. Old enterprise IAM products (older ADFS, legacy Java SSO frameworks, embedded device firmware) often only support RS256. If you're integrating with any unknown identity ecosystem, RS256 is the highest-confidence default.

PS256 wins on modern stacks

Every modern library, node-jose, jose-jwt, python-jose, golang-jwt, .NET Microsoft.IdentityModel, PyJWT, Auth0's auth0/node-jsonwebtoken (v9+), supports PS256 first-class. OpenID Connect's self-issued OPs, FAPI 2.0 profiles, and most fintech compliance regimes mandate PS256 (or PS384/PS512) and forbid RS256 in new deployments.

Signature and Key Sizes

Both produce identical-size signatures for a given RSA key size: 256 bytes for 2048-bit RSA, 512 bytes for 4096-bit. The base64url-encoded signature in the JWT is ~342 characters (2048-bit) regardless of which padding is used. No bandwidth tradeoff between the two, only ES256/EdDSA save bytes.

Migrating from RS256 to PS256

Because the key pair is shared, the migration is a "header swap" not a "key rotation":

  • ·Update the issuer to sign tokens with alg: PS256 using the existing private key.
  • ·Update every verifier's allow-list to accept BOTH RS256 and PS256 during the transition window.
  • ·After all in-flight tokens expire (one access-token lifetime), remove RS256 from the allow-list.
  • ·Update the JWKS endpoint's alg field on the key to "PS256".
DO NOT silently switch issuance to PS256 without first updating every verifier's allow-list. Verifiers that only accept RS256 will reject your new PS256 tokens. The "allow both, then narrow" pattern is the safe rollout.

The Algorithm Confusion Pitfall (Applies to Both)

Both PS256 and RS256 are vulnerable to algorithm-confusion attacks if your verifier picks the verification algorithm from the JWT's own alg header rather than from a server-side allow-list. The classic attack: attacker takes a token meant to be RS256-signed, changes the header to HS256, and "signs" it using the public key as the HMAC secret. A naive verifier reads alg: HS256, fetches the RSA public key (treated as a byte string), runs HMAC-SHA256, and accepts.

The fix is universal: always pick the verification algorithm from your code or configuration, not from the token header. Switching from RS256 to PS256 doesn't help here, the attack works the same way against PS256 verifiers that trust the header.

Decision Guide

  • ·New system, full control, no legacy partners → ES256 first, PS256 if RSA infrastructure is already in place.
  • ·Mixed legacy ecosystem, need broadest interop → RS256, with intent to migrate to PS256 later.
  • ·Fintech, FAPI, OIDC self-issued, compliance regime mandate → PS256 (or whatever the regime mandates explicitly).
  • ·HSM-based signing → Confirm the HSM supports RSA-PSS before choosing it; some older HSMs only do PKCS#1 v1.5.

Verifying a PS256 Token

jwtdecode.app verifies PS256 alongside RS256 using the browser's Web Crypto API (SubtleCrypto.verify("RSA-PSS", ...)). Paste any token withalg: PS256 and the matching public key into the JWT decoder, verification runs locally, the token never leaves your browser.

Summary

PS256 is RS256's modern replacement at the same key size: provably secure, randomised, and free of the recurring PKCS#1 v1.5 implementation pitfalls. They share key infrastructure, so migration is a header swap not a key rotation. RS256 stays the right choice when broad legacy interop is the constraint; PS256 is the right choice when compliance or threat-modelling pushes for the stronger padding. Either way, allow-list algorithms server-side, algorithm confusion attacks bite both.

Ready to decode a token?
Use the free JWT decoder — paste any token for instant results, entirely in your browser.
Open JWT Decoder