{"title":"Cryptographic Weakness via ECB Mode Block Cipher with Deterministic Padding in AES Encryption","language":"Python","severity":"High","cwe":"CWE-327","source_lines":[4],"flow_lines":[4,5,6,7,8],"sink_lines":[7,8],"vulnerable_code":"from Crypto.Cipher import AES\nimport hashlib\ndef vault_seal_payload(secret_token, tenant_id):\n    raw_key = hashlib.md5(tenant_id.encode()).hexdigest()[:16]\n    blk_cipher = AES.new(raw_key.encode(), AES.MODE_ECB)\n    pad_len = 16 - (len(secret_token) % 16)\n    padded_payload = secret_token + chr(pad_len) * pad_len\n    sealed = blk_cipher.encrypt(padded_payload.encode())\n    return sealed.hex()","explanation":"The code uses AES in ECB (Electronic Codebook) mode, which is cryptographically insecure because identical plaintext blocks always produce identical ciphertext blocks, revealing data patterns. Additionally, the encryption key is derived using MD5 (a weak hashing algorithm) from the tenant_id, making the key predictable and vulnerable to brute-force attacks. The deterministic padding (chr(pad_len)) further weakens the scheme by making padding predictable and enabling padding oracle-style attacks.","remediation":"The fix replaces the insecure ECB mode with AES-GCM, which provides both confidentiality and authenticity through authenticated encryption with a random nonce, preventing pattern leakage and tampering. The weak MD5-based key derivation is replaced with PBKDF2-HMAC-SHA256 using a random 16-byte salt and 100,000 iterations, making brute-force key recovery computationally infeasible. Manual deterministic padding is eliminated since GCM mode is a stream-based mode that does not require block padding.","secure_code":"from Crypto.Cipher import AES\nfrom Crypto.Random import get_random_bytes\nimport hashlib\nimport os\n\ndef vault_seal_payload(secret_token, tenant_id):\n    # Derive a strong 256-bit key using PBKDF2 with a random salt\n    salt = get_random_bytes(16)\n    raw_key = hashlib.pbkdf2_hmac('sha256', tenant_id.encode(), salt, iterations=100000)\n    # Use AES-GCM for authenticated encryption with a random IV/nonce\n    nonce = get_random_bytes(12)\n    cipher = AES.new(raw_key, AES.MODE_GCM, nonce=nonce)\n    ciphertext, tag = cipher.encrypt_and_digest(secret_token.encode())\n    # Prepend salt + nonce + tag to ciphertext so decryption can recover them\n    sealed = salt + nonce + tag + ciphertext\n    return sealed.hex()\n\ndef vault_unseal_payload(sealed_hex, tenant_id):\n    sealed = bytes.fromhex(sealed_hex)\n    # Extract salt (16 bytes), nonce (12 bytes), tag (16 bytes), and ciphertext\n    salt = sealed[:16]\n    nonce = sealed[16:28]\n    tag = sealed[28:44]\n    ciphertext = sealed[44:]\n    # Re-derive the key using the same salt\n    raw_key = hashlib.pbkdf2_hmac('sha256', tenant_id.encode(), salt, iterations=100000)\n    cipher = AES.new(raw_key, AES.MODE_GCM, nonce=nonce)\n    secret_token = cipher.decrypt_and_verify(ciphertext, tag)\n    return secret_token.decode()"}