From b5a03173543939a79de6b90214e25622ce4bc023 Mon Sep 17 00:00:00 2001 From: neeraj netrakar Date: Mon, 19 Oct 2026 17:11:10 +0530 Subject: [PATCH 1/2] Check the JWE auth tag before decrypting CBC-HMAC ciphertext --- jose/jwe.py | 14 +++++++------- tests/test_jwe.py | 21 ++++++++++++++++++++- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/jose/jwe.py b/jose/jwe.py index 09e5c32..f229476 100644 --- a/jose/jwe.py +++ b/jose/jwe.py @@ -1,4 +1,5 @@ import binascii +import hmac import json import zlib from collections.abc import Mapping @@ -237,17 +238,16 @@ def _decrypt_and_auth(cek_bytes, enc, cipher_text, iv, aad, auth_tag): if enc in ALGORITHMS.HMAC_AUTH_TAG: encryption_key, mac_key, key_len = _get_encryption_key_mac_key_and_key_length_from_cek(cek_bytes, enc) auth_tag_check = _auth_tag(cipher_text, iv, aad, mac_key, key_len) + # Validate the tag before decrypting so that a forged ciphertext is + # rejected without ever being decrypted or unpadded. + if not hmac.compare_digest(auth_tag, auth_tag_check): + raise JWEError("Invalid JWE Auth Tag") elif enc in ALGORITHMS.GCM: - encryption_key = jwk.construct(cek_bytes, enc) - auth_tag_check = auth_tag # GCM check auth on decrypt + encryption_key = jwk.construct(cek_bytes, enc) # GCM check auth on decrypt else: raise NotImplementedError(f"enc {enc} is not implemented!") - plaintext = encryption_key.decrypt(cipher_text, iv, aad, auth_tag) - if auth_tag != auth_tag_check: - raise JWEError("Invalid JWE Auth Tag") - - return plaintext + return encryption_key.decrypt(cipher_text, iv, aad, auth_tag) def _get_encryption_key_mac_key_and_key_length_from_cek(cek_bytes, enc): diff --git a/tests/test_jwe.py b/tests/test_jwe.py index 6ab9971..4897a13 100644 --- a/tests/test_jwe.py +++ b/tests/test_jwe.py @@ -7,7 +7,7 @@ from jose.constants import ALGORITHMS, ZIPS from jose.exceptions import JWEError, JWEParseError from jose.jwk import AESKey, RSAKey -from jose.utils import base64url_decode +from jose.utils import base64url_decode, base64url_encode backends = [] try: @@ -336,6 +336,25 @@ def test_decrypt_oct_256_key_wrap(self, jwe_package): actual = jwe.decrypt(jwe_package, key) assert actual == b"Live long and prosper." + @pytest.mark.parametrize( + "enc, key", + ( + (ALGORITHMS.A128CBC_HS256, OCT_256_BIT_KEY), + (ALGORITHMS.A192CBC_HS384, OCT_384_BIT_KEY), + (ALGORITHMS.A256CBC_HS512, OCT_512_BIT_KEY), + ), + ) + def test_tampered_cipher_text_is_auth_tag_error(self, enc, key): + encrypted = jwe.encrypt(b"Live long and prosper.", key, enc, ALGORITHMS.DIR) + header, encrypted_key, iv, cipher_text, auth_tag = encrypted.split(b".") + tampered = bytearray(base64url_decode(cipher_text)) + tampered[-1] ^= 0x01 + cipher_text = base64url_encode(bytes(tampered)) + encrypted = b".".join((header, encrypted_key, iv, cipher_text, auth_tag)) + with pytest.raises(JWEError) as excinfo: + jwe.decrypt(encrypted, key) + assert str(excinfo.value) == "Invalid JWE Auth Tag" + def test_invalid_jwe_is_parse_error(self): with pytest.raises(JWEParseError): jwe.decrypt("invalid", "key") From d9c002f88a0ec12435c4dcca8c1eeb5779414fd2 Mon Sep 17 00:00:00 2001 From: neeraj netrakar Date: Fri, 23 Oct 2026 12:44:50 +0530 Subject: [PATCH 2/2] Make the tampered ciphertext test deterministic --- tests/test_jwe.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_jwe.py b/tests/test_jwe.py index 4897a13..c898dce 100644 --- a/tests/test_jwe.py +++ b/tests/test_jwe.py @@ -348,7 +348,10 @@ def test_tampered_cipher_text_is_auth_tag_error(self, enc, key): encrypted = jwe.encrypt(b"Live long and prosper.", key, enc, ALGORITHMS.DIR) header, encrypted_key, iv, cipher_text, auth_tag = encrypted.split(b".") tampered = bytearray(base64url_decode(cipher_text)) - tampered[-1] ^= 0x01 + # Flip the last byte of the second-to-last block so that only the final + # padding byte of the plaintext changes; flipping a byte in the last + # block would scramble the whole block and could leave valid padding. + tampered[-17] ^= 0x01 cipher_text = base64url_encode(bytes(tampered)) encrypted = b".".join((header, encrypted_key, iv, cipher_text, auth_tag)) with pytest.raises(JWEError) as excinfo: