Bleichenbacher’s Ghost Returns: How a 1998 Attack Broke Authlib in 2026 (CVE-2026-28490)?
In the high-stakes world of modern authentication, we like to believe our libraries are battle-hardened fortresses. OAuth and OpenID Connect servers hum along, tokens fly encrypted through the ether, and we sleep easy knowing cryptography has our back.
Then, on March 15, 2026, a single line of Python quietly shattered that illusion.
Enter CVE-2026-28490 — a high-severity (CVSS v4.0: 8.3) cryptographic padding oracle in the wildly popular Authlib library. This wasn’t some exotic zero-day crafted in a nation-state lab. It was the 1998 Bleichenbacher attack, resurrected like a cryptographic zombie, strolling right through the front door of a library that powers Flask, Django, FastAPI, and countless production OAuth servers worldwide.
The Setup: When “Secure by Default” Becomes Dangerously Default
Authlib is the Swiss Army knife for building OAuth 2.0 and OpenID Connect servers in Python. Developers love it for its elegance: one JsonWebEncryption() call and you’re encrypting tokens with JSON Web Encryption (JWE).
By design, Authlib registers the legacy RSA1_5 algorithm (RSAES-PKCS1-v1_5) in its default algorithm registry. No opt-in required. No scary warning. Just… there.
RSA1_5 uses the exact same PKCS#1 v1.5 padding that Daniel Bleichenbacher famously broke in 1998. The cryptography community has been screaming “deprecated!” for decades. RFC 7516 even warns against it. Yet here we were in 2026, still shipping it by default.
The Flaw: One Exception That Whispered Too Loud
Here’s where the magic (and the tragedy) happened.
Under the hood, Authlib leans on the rock-solid cryptography library. When cryptography sees invalid PKCS#1 v1.5 padding during RSA decryption, it doesn’t scream “INVALID!” — it returns a randomized blob of bytes (exactly as RFC 3218 §2.3.2 demands) to prevent oracle attacks.
Authlib, however, immediately ran its own length check on the decrypted Content Encryption Key (CEK):
if len(cek) * 8 != enc_alg.CEK_SIZE:raise ValueError('Invalid "cek" length')
That single raise created two perfectly distinguishable failure paths:
- Invalid padding → random bytes → length mismatch → ValueError
- Valid padding but bad MAC → correct length → AES-GCM tag failure → InvalidTag
An attacker with a network connection and the server’s public key (usually public via /.well-known/jwks.json) could now run a classic Bleichenbacher oracle attack. By sending thousands of crafted JWE tokens and watching which exception leaked back (through HTTP response bodies, logging, or even Sentry traces), they could recover the CEK byte-by-byte.
Result? Full decryption of intercepted tokens and the ability to forge new valid ones — all without ever touching the private key.
Confidentiality impact: Catastrophic.
Why This Matters More Than Another CVE
Most padding-oracle bugs stay theoretical. This one didn’t.
- It was enabled by default.
- It bypassed the very mitigation cryptography had implemented.
- It worked even when timing differences were negligible (the exception oracle was loud enough).
- It affected every application quietly relying on Authlib for JWE token encryption.
In 2026, with AI agents, microservices, and zero-trust architectures everywhere, a single compromised CEK could cascade into full session hijacking across federated identity systems.
This wasn’t just a bug. It was a reminder that crypto primitives are contracts — and Authlib accidentally tore up the most important clause.
The Fix: Deprecation Done Right (Finally)
Authlib responded swiftly. Version 1.6.9 (released shortly after disclosure) marks RSA1_5 as deprecated and removes it from the default algorithm registry entirely.
The library now raises UnsupportedAlgorithmError for any deprecated algorithm unless you explicitly opt in — with full awareness of the risks. The maintainers also updated validation logic across JWE and JWS handling to enforce this cleanly.
If you still need legacy RSA1_5 (you really shouldn’t), you’ll now have to jump through deliberate hoops and accept the warnings.
What You Must Do Today
- Upgrade immediately — pip install authlib>=1.6.9
- Audit your code — Search for JsonWebEncryption(), RSA1_5, or any JWE usage. Switch to RSA-OAEP (or better, ECDH-ES / dir + AES-GCM) wherever possible.
- Review error handling — Never let exception types or response bodies leak crypto details. Wrap your auth endpoints in consistent error responses.
- Scan your dependencies — If you use any framework or SaaS that bundles Authlib, check their patch status today.
- Retire legacy algorithms — Treat RSA1_5 like the 1998 Windows 98 floppy disk it is.
The Bigger Lesson for the Industry
CVE-2026-28490 is not an isolated incident. It’s a symptom of a deeper truth: the most dangerous code is the code we trust without understanding.
We’ve spent decades building layers of abstraction so developers don’t have to be cryptographers. But when those abstractions hide deprecated algorithms, skip RFC-mandated constant-time behavior, or treat legacy padding as “good enough,” we’re not abstracting complexity — we’re inheriting debt.
In 2026, with quantum threats on the horizon and AI-powered attackers on the rise, the margin for “good enough” crypto is zero.
Bleichenbacher’s attack should have died in the 20th century. Instead, it waited patiently in a popular library’s default settings, ready to strike the moment someone trusted the wrong line of code.
The ghost is finally exorcised in Authlib 1.6.9.
But how many other libraries are still carrying its cousins in their default registries?
Upgrade. Audit. Deprecate. Repeat.
Your tokens — and your users’ trust — depend on it.