# AES-GCM Nonce Reuse Leading to Keystream Disclosure

Language: Python
Severity: Critical
CWE: CWE-323

## Source
8

## Flow
8-12

## Sink
12

## Vulnerable Code
```python
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

class IoTSensorEncryptor:
    def __init__(self):
        self.master_key = AESGCM.generate_key(bit_length=256)
        self.cipher = AESGCM(self.master_key)
        self.device_nonce = os.urandom(12)
    
    def encrypt_telemetry(self, sensor_data, device_id):
        associated = f"device:{device_id}".encode()
        encrypted = self.cipher.encrypt(self.device_nonce, sensor_data.encode(), associated)
        return encrypted.hex()
    
    def transmit_batch(self, readings):
        return [self.encrypt_telemetry(reading['temp'], reading['id']) for reading in readings]
```

## Explanation

The nonce is generated once during initialization (line 8) and stored as an instance variable, then reused for every encryption operation (line 12). AES-GCM requires a unique nonce for each encryption with the same key; reusing nonces catastrophically breaks confidentiality by allowing attackers to recover the keystream and decrypt all messages.

## Remediation

The fix generates a fresh random 12-byte nonce for each encryption operation instead of reusing a single instance-level nonce. The nonce is prepended to the ciphertext so the receiver can extract it for decryption. This ensures AES-GCM's requirement of nonce uniqueness per key is satisfied.

## Secure Code
```python
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

class IoTSensorEncryptor:
    def __init__(self):
        self.master_key = AESGCM.generate_key(bit_length=256)
        self.cipher = AESGCM(self.master_key)
    
    def encrypt_telemetry(self, sensor_data, device_id):
        nonce = os.urandom(12)
        associated = f"device:{device_id}".encode()
        encrypted = self.cipher.encrypt(nonce, sensor_data.encode(), associated)
        return (nonce + encrypted).hex()
    
    def transmit_batch(self, readings):
        return [self.encrypt_telemetry(reading['temp'], reading['id']) for reading in readings]
```
