From b5a03173543939a79de6b90214e25622ce4bc023 Mon Sep 17 00:00:00 2001 From: neeraj netrakar Date: Mon, 19 Oct 2026 17:11:10 +0530 Subject: [PATCH] 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")