{"title":"AES-GCM Authentication Tag Omission","language":"Python","severity":"High","cwe":"CWE-345","source_lines":[8],"flow_lines":[5,6,7,8,9],"sink_lines":[9],"vulnerable_code":"from Crypto.Cipher import AES\nimport os\n\ndef encrypt_iot_telemetry(device_id, sensor_data):\n    master_key = os.environ.get('IOT_MASTER_KEY', 'default_key_1234').encode().ljust(32)[:32]\n    nonce = os.urandom(12)\n    cipher = AES.new(master_key, AES.MODE_GCM, nonce=nonce)\n    ciphertext = cipher.encrypt(sensor_data.encode())\n    encrypted_payload = nonce + ciphertext\n    return encrypted_payload.hex()\n\ndef decrypt_iot_telemetry(device_id, encrypted_hex):\n    master_key = os.environ.get('IOT_MASTER_KEY', 'default_key_1234').encode().ljust(32)[:32]\n    encrypted_payload = bytes.fromhex(encrypted_hex)\n    nonce = encrypted_payload[:12]\n    ciphertext = encrypted_payload[12:]\n    cipher = AES.new(master_key, AES.MODE_GCM, nonce=nonce)\n    plaintext = cipher.decrypt(ciphertext)\n    return plaintext.decode()","explanation":"The code uses AES-GCM mode but fails to generate and verify the authentication tag. Without calling cipher.digest() after encryption and cipher.verify() before decryption, the encrypted data has no integrity protection, allowing attackers to tamper with ciphertext without detection.","remediation":"The fix uses encrypt_and_digest() to generate both ciphertext and the 16-byte authentication tag during encryption, and decrypt_and_verify() during decryption to validate the tag before returning plaintext. The device_id is also included as authenticated associated data (AAD) via cipher.update() to bind the ciphertext to the specific device, preventing cross-device replay attacks. The payload format is now nonce (12 bytes) + tag (16 bytes) + ciphertext.","secure_code":"from Crypto.Cipher import AES\nimport os\n\ndef encrypt_iot_telemetry(device_id, sensor_data):\n    master_key = os.environ.get('IOT_MASTER_KEY', 'default_key_1234').encode().ljust(32)[:32]\n    nonce = os.urandom(12)\n    cipher = AES.new(master_key, AES.MODE_GCM, nonce=nonce)\n    cipher.update(device_id.encode())\n    ciphertext, tag = cipher.encrypt_and_digest(sensor_data.encode())\n    encrypted_payload = nonce + tag + ciphertext\n    return encrypted_payload.hex()\n\ndef decrypt_iot_telemetry(device_id, encrypted_hex):\n    master_key = os.environ.get('IOT_MASTER_KEY', 'default_key_1234').encode().ljust(32)[:32]\n    encrypted_payload = bytes.fromhex(encrypted_hex)\n    nonce = encrypted_payload[:12]\n    tag = encrypted_payload[12:28]\n    ciphertext = encrypted_payload[28:]\n    cipher = AES.new(master_key, AES.MODE_GCM, nonce=nonce)\n    cipher.update(device_id.encode())\n    plaintext = cipher.decrypt_and_verify(ciphertext, tag)\n    return plaintext.decode()"}