Skip to content

[Security] RSA1_5 JWE decryption violates RFC 7516 §11.5: wrong-length unwrapped CEK escapes random-CEK substitution and reaches AES key constructor #408

@lullu57

Description

@lullu57

Summary

python-jose's JWE decryption with alg=RSA1_5 violates the RFC 7516 §11.5 mitigation against Bleichenbacher-style attacks on PKCS#1 v1.5. The recipient is required to "MUST NOT distinguish between format, padding, and length errors of encrypted keys" and to substitute a random CEK on any unwrap failure. jose/jwe.py implements the substitution for the exception path only — when unwrap_key() raises — but does not validate the length of a successfully-returned unwrap output before passing it forward.

When the underlying RSA PKCS1v15 implementation in pyca/cryptography returns bytes that are not the expected CEK size for the JWE enc algorithm (which is the expected behavior of its constant-time fallback for some padding-error classes), those bytes flow into _decrypt_and_auth. The AES key constructor then raises a length-specific JWKError with a message that is distinguishable from the "random-CEK substitution → MAC fails"
path.

This is the same vulnerability class as Authlib's
CVE-2026-28490 / GHSA-7432-952r-cw78

("JWE RSA1_5 Bleichenbacher Padding Oracle", severity High), fixed in
Authlib 1.6.9. Authlib raised a length-specific ValueError('Invalid "cek" length'); python-jose raises a length-specific
JWKError("Key must be N bit for alg <X>"). Different message, same
distinguishability problem.

Affected versions

3.4.0 and 3.5.0 confirmed. Earlier versions on the same
jose/jwe.py + jose/backends/cryptography_backend.py code paths are
likely affected.

Affected code

  • Sink (exception substitution): jose/jwe.py lines ~140–159 — the try / except that substitutes `_get_random_cek_bytes_for_enc(enc)` on failure does not check the length of `cek_bytes` when the call succeeds.
  • Distinguishable error site: jose/backends/cryptography_backend.py lines ~461–470 — `CryptographyAESKey.init` raises `JWKError("Key must be N bit for alg ")` when the unwrapped CEK is the wrong length for the JWE `enc` algorithm.

Standards background

Both RFCs explicitly call out length errors alongside format and
padding errors as conditions the verifier MUST NOT distinguish.

Bug class

CWE-208 (Observable Response Discrepancy) / CWE-310 (Cryptographic
Issues). Specifically: Bleichenbacher-style oracle precursor. Whether
the oracle is practically exploitable in a given deployment depends on
whether the application surfaces the distinct exception class or
message to the requesting party (response body, error log accessible
via a separate channel, response-time differential, etc.).

The same vulnerability class is publicly recorded as:

No python-jose issue currently covers the wrong-length CEK
distinguishability path in cryptography_backend.py specifically.
Issues that look related but are not duplicates:

  • #398 — algorithm confusion / empty HMAC key / non-constant-time JWE auth-tag comparison. Different.
  • #402 — AES-CBC JWE padding oracle. Distinct vector; reachable only AFTER CEK recovery.

Proposed fix

In jose/jwe.py, immediately after the unwrap_key call, validate the
returned CEK length against the JWE enc algorithm and treat any
length mismatch identically to an unwrap exception — collapse to the
random-CEK substitution path so the post-decrypt error becomes the
canonical "Invalid JWE Auth Tag":

EXPECTED_CEK_BYTES = {
    "A128CBC-HS256": 32, "A192CBC-HS384": 48, "A256CBC-HS512": 64,
    "A128GCM":       16, "A192GCM":       24, "A256GCM":       32,
}

try:
    cek_bytes = key.unwrap_key(encrypted_key)
    if len(cek_bytes) != EXPECTED_CEK_BYTES[enc]:
        raise ValueError("wrong CEK length")
    cek_valid = True
except NotImplementedError:
    raise JWEError(f"alg {alg} is not implemented")
except Exception:
    cek_valid = False
    cek_bytes = _get_random_cek_bytes_for_enc(enc)

After this fix, both the "PKCS1v15 unwrap failed" path and the
"PKCS1v15 unwrap returned wrong length" path produce the same
random-CEK substitution and the same downstream "Invalid JWE Auth Tag"
error, restoring RFC 7516 §11.5 compliance for the RSA1_5 + CBC-HMAC
family.

Recommended user-side mitigation in the meantime

Given the project's current maintenance cadence (last release Jan 2024; no SECURITY.md; see #330), applications that accept JWE tokens with alg=RSA1_5 from untrusted parties should consider migrating to a JWE library where this class is already fixed, e.g.:

Independently, the IETF JOSE WG draft `draft-ietf-jose-deprecate-none-rsa15`
deprecates RSA1_5 ecosystem-wide; new deployments should pick
RSA-OAEP, RSA-OAEP-256, or an ECDH-ES variant.

Disclosure note

This is a public issue because python-jose does not have private
vulnerability reporting enabled and has no SECURITY.md. The same
disclosure pattern has been used by other reporters in #195, #274, #377, #398, #399, #402, #403, and #407.

The working PoC, patch, and CVSS rationale are intentionally not
included in this issue to limit ready exploitation while the bug is
unfixed. They are available privately on request through the reporter's
GitHub profile.

— Reported as part of a small peer-review bundle of runtime security
findings

cc: anyone who has been triaging recent security issues here — happy to
provide more detail privately if a maintainer wants to take this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions