# Cryptographic Weakness via ECB Mode Block Cipher with Deterministic Padding in AES Encryption

Language: Python
Severity: High
CWE: CWE-327

## Source
4

## Flow
4-5-6-7-8

## Sink
7-8

## Vulnerable Code
```python
from Crypto.Cipher import AES
import hashlib
def vault_seal_payload(secret_token, tenant_id):
    raw_key = hashlib.md5(tenant_id.encode()).hexdigest()[:16]
    blk_cipher = AES.new(raw_key.encode(), AES.MODE_ECB)
    pad_len = 16 - (len(secret_token) % 16)
    padded_payload = secret_token + chr(pad_len) * pad_len
    sealed = blk_cipher.encrypt(padded_payload.encode())
    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
```python
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import hashlib
import os

def vault_seal_payload(secret_token, tenant_id):
    # Derive a strong 256-bit key using PBKDF2 with a random salt
    salt = get_random_bytes(16)
    raw_key = hashlib.pbkdf2_hmac('sha256', tenant_id.encode(), salt, iterations=100000)
    # Use AES-GCM for authenticated encryption with a random IV/nonce
    nonce = get_random_bytes(12)
    cipher = AES.new(raw_key, AES.MODE_GCM, nonce=nonce)
    ciphertext, tag = cipher.encrypt_and_digest(secret_token.encode())
    # Prepend salt + nonce + tag to ciphertext so decryption can recover them
    sealed = salt + nonce + tag + ciphertext
    return sealed.hex()

def vault_unseal_payload(sealed_hex, tenant_id):
    sealed = bytes.fromhex(sealed_hex)
    # Extract salt (16 bytes), nonce (12 bytes), tag (16 bytes), and ciphertext
    salt = sealed[:16]
    nonce = sealed[16:28]
    tag = sealed[28:44]
    ciphertext = sealed[44:]
    # Re-derive the key using the same salt
    raw_key = hashlib.pbkdf2_hmac('sha256', tenant_id.encode(), salt, iterations=100000)
    cipher = AES.new(raw_key, AES.MODE_GCM, nonce=nonce)
    secret_token = cipher.decrypt_and_verify(ciphertext, tag)
    return secret_token.decode()
```
