diff --git a/imptune/generators/intunewin_builder.py b/imptune/generators/intunewin_builder.py index b3d8db4..b991667 100644 --- a/imptune/generators/intunewin_builder.py +++ b/imptune/generators/intunewin_builder.py @@ -77,8 +77,13 @@ def build_intunewin(source_dir: str, setup_file: str, output_path: str) -> None: cipher = AES.new(aes_key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(pad(plaintext, AES.block_size)) - # --- Step 4: Compute HMAC-SHA256 over ciphertext (using mac_key) --- - mac_digest = hmac.new(mac_key, ciphertext, hashlib.sha256).digest() + # --- Step 4: Compute HMAC-SHA256 over (IV + ciphertext) using mac_key --- + # The reference (svrooij/ContentPrep Zipper.cs DecryptFileAsync) reads the first + # 32 bytes as the stored HMAC, then computes the hash of the *remaining* bytes + # (= IV || ciphertext) to verify integrity. Authenticated-encryption best + # practice (Encrypt-then-MAC) also requires the IV to be covered by the MAC so + # that a forged IV cannot redirect decryption. + mac_digest = hmac.new(mac_key, iv + ciphertext, hashlib.sha256).digest() # --- Step 5: Assemble encrypted blob: [HMAC(32)] + [IV(16)] + [ciphertext] --- encrypted_blob = mac_digest + iv + ciphertext diff --git a/tests/test_intunewin.py b/tests/test_intunewin.py index f1f7093..14b2a05 100644 --- a/tests/test_intunewin.py +++ b/tests/test_intunewin.py @@ -191,27 +191,33 @@ class TestEncryptedBlobLayout: class TestCryptographicVerification: def test_hmac_matches(self, package_contents): - """HMAC-SHA256 of ciphertext matches first 32 bytes of blob AND Mac in Detection.xml.""" + """HMAC-SHA256 of (IV || ciphertext) matches first 32 bytes of blob AND Mac in Detection.xml. + + The reference implementation (svrooij/ContentPrep Zipper.cs DecryptFileAsync) reads + the first 32 bytes as the stored HMAC, then computes the hash of the *remaining* bytes + (bytes[32:] = IV || ciphertext) to verify integrity. The IV MUST be included in the + HMAC so that a forged IV cannot redirect decryption without being detected. + """ blob = package_contents["blob"] tree = package_contents["tree"] blob_hmac = blob[:32] - ciphertext = blob[48:] + iv_and_ciphertext = blob[32:] # IV (16 bytes) + ciphertext — what the reference hashes mac_key_b64 = _get_enc_text(tree, "MacKey") mac_key = base64.b64decode(mac_key_b64) - computed_hmac = hmac.new(mac_key, ciphertext, hashlib.sha256).digest() + computed_hmac = hmac.new(mac_key, iv_and_ciphertext, hashlib.sha256).digest() # Must match the blob header assert computed_hmac == blob_hmac, ( - "HMAC-SHA256 of ciphertext does not match the first 32 bytes of the blob" + "HMAC-SHA256 of (IV || ciphertext) does not match the first 32 bytes of the blob" ) # Must also match Detection.xml Mac field xml_mac = base64.b64decode(_get_enc_text(tree, "Mac")) assert computed_hmac == xml_mac, ( - "HMAC-SHA256 of ciphertext does not match the Mac value in Detection.xml" + "HMAC-SHA256 of (IV || ciphertext) does not match the Mac value in Detection.xml" ) def test_decryption_roundtrip(self, source_dir, package_contents):