AES-256 Security-Focused API Documentation

Version: 1.0
Last Updated: 2025-07-05
**Security Classification:
PUBLIC
Author: Phantom (phantom@metamui.id)

Overview

AES-256 (Advanced Encryption Standard with 256-bit keys) is a symmetric block cipher standardized by NIST. It operates on 128-bit blocks and is widely deployed with hardware acceleration on modern processors. This documentation covers secure usage of AES-256 across different modes of operation.

Security Level: 256-bit key, 128-bit block
Key Size: 32 bytes (256 bits)
Block Size: 16 bytes (128 bits)
Rounds: 14

Security Warnings ⚠️

  1. Mode Selection Critical: ECB mode is NEVER safe - use GCM, CTR, or CBC
  2. IV/Nonce Uniqueness: Reusing IV/nonce with same key breaks security
  3. Padding Oracle: CBC mode vulnerable without authenticated encryption
  4. Block Size Limit: Birthday bound at 2^64 blocks (~256 EB) per key
  5. Not Authenticated: AES alone doesn’t provide authenticity - use AES-GCM

API Functions

generate_key() -> bytes[32]

Security Contract:

Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Weak Keys | ✅ | No weak keys in AES | | Related Key | ✅ | With proper generation | | Predictability | ⚠️ | Depends on RNG quality | | Key Recovery | ✅ | 2^256 operations |

Secure Usage Example:

import os
import secrets
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend

class AES256KeyManager:
    def __init__(self):
        self.keys = {}
        self.key_lifetime = 30 * 24 * 3600  # 30 days
        
    def generate_data_key(self) -> tuple:
        """Generate new data encryption key"""
        
        # Generate AES-256 key
        key = secrets.token_bytes(32)
        
        # Generate key ID
        key_id = secrets.token_urlsafe(16)
        
        # Store with metadata
        self.keys[key_id] = {
            'key': key,
            'created': time.time(),
            'usage_count': 0,
            'bytes_encrypted': 0
        }
        
        return key_id, key
    
    def rotate_keys(self):
        """Rotate old or overused keys"""
        current_time = time.time()
        
        for key_id, key_data in list(self.keys.items()):
            # Check age
            if current_time - key_data['created'] > self.key_lifetime:
                self._retire_key(key_id)
                continue
            
            # Check usage limits (NIST recommends)
            if key_data['bytes_encrypted'] > 2**39:  # ~550 GB
                self._retire_key(key_id)
                continue
            
            if key_data['usage_count'] > 2**32:
                self._retire_key(key_id)
    
    def _retire_key(self, key_id: str):
        """Securely retire a key"""
        key_data = self.keys[key_id]
        
        # Re-encrypt data under new key
        new_key_id, new_key = self.generate_data_key()
        self._reencrypt_data(key_data['key'], new_key)
        
        # Securely delete old key
        secure_zero(key_data['key'])
        del self.keys[key_id]

# SECURE: Key wrapping for storage
def wrap_aes_key(kek: bytes, key: bytes) -> bytes:
    """AES Key Wrap (RFC 3394) for secure key storage"""
    from cryptography.hazmat.primitives.keywrap import aes_key_wrap
    
    # Wrap the key
    wrapped = aes_key_wrap(
        wrapping_key=kek,
        key_to_wrap=key,
        backend=default_backend()
    )
    
    return wrapped

# SECURE: Hardware security module integration
class HSMKeyManager:
    def __init__(self, hsm_client):
        self.hsm = hsm_client
        
    def generate_aes_key(self, key_label: str) -> str:
        """Generate AES key in HSM"""
        
        key_spec = {
            'algorithm': 'AES',
            'length': 256,
            'extractable': False,
            'usage': ['ENCRYPT', 'DECRYPT']
        }
        
        # Key never leaves HSM
        key_handle = self.hsm.generate_key(key_label, key_spec)
        
        return key_handle

Common Mistakes:

# INSECURE: Weak key generation
key = hashlib.sha256(b"my password").digest()  # NOT RANDOM!

# INSECURE: Predictable keys
key = os.urandom(32)  # OK on Unix, bad on some systems
# Use: key = secrets.token_bytes(32)

# INSECURE: Key reuse across applications
GLOBAL_KEY = b"0123456789abcdef" * 2  # NEVER hardcode!

# INSECURE: Not rotating keys
# Using same key for years without rotation

encrypt_gcm(key: bytes[32], plaintext: bytes, associated_data: bytes = b"") -> (bytes, bytes[16], bytes[12])

Security Contract:

Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Confidentiality | ✅ | AES-256 encryption | | Authenticity | ✅ | GCM authentication | | Nonce Reuse | ❌ | Catastrophic if reused | | Forgery | ✅ | 2^128 attempts needed | | Padding Oracle | ✅ | No padding in GCM |

Security Requirements:

Secure Usage Example:

use aes_gcm::{
    aead::{Aead, KeyInit, OsRng},
    Aes256Gcm, Nonce, Key
};

pub struct SecureAesGcm {
    cipher: Aes256Gcm,
    nonce_counter: u64,
}

impl SecureAesGcm {
    pub fn new(key: &[u8; 32]) -> Self {
        let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key));
        
        Self {
            cipher,
            nonce_counter: 0,
        }
    }
    
    pub fn encrypt_with_ad(
        &mut self,
        plaintext: &[u8],
        associated_data: &[u8],
    ) -> Result<EncryptedData, Error> {
        // Generate unique nonce
        let nonce = self.generate_nonce()?;
        
        // Create payload with associated data
        let payload = Payload {
            msg: plaintext,
            aad: associated_data,
        };
        
        // Encrypt
        let ciphertext = self.cipher
            .encrypt(&nonce, payload)
            .map_err(|_| Error::EncryptionFailed)?;
        
        // Split ciphertext and tag
        let (ct, tag) = ciphertext.split_at(ciphertext.len() - 16);
        
        Ok(EncryptedData {
            nonce: nonce.to_vec(),
            ciphertext: ct.to_vec(),
            tag: tag.to_vec(),
            aad: associated_data.to_vec(),
        })
    }
    
    fn generate_nonce(&mut self) -> Result<Nonce<U12>, Error> {
        // Counter-based nonce generation
        if self.nonce_counter >= 2u64.pow(32) {
            return Err(Error::NonceExhaustion);
        }
        
        let mut nonce_bytes = [0u8; 12];
        nonce_bytes[..8].copy_from_slice(&self.nonce_counter.to_le_bytes());
        
        self.nonce_counter += 1;
        
        Ok(Nonce::from_slice(&nonce_bytes).copied())
    }
}

// SECURE: File encryption with AES-GCM
pub fn encrypt_file(
    file_path: &Path,
    key: &[u8; 32],
) -> Result<(), Error> {
    let metadata = std::fs::metadata(file_path)?;
    
    // Read file
    let plaintext = std::fs::read(file_path)?;
    
    // Create associated data from metadata
    let ad = format!(
        "file:{},size:{},modified:{}",
        file_path.display(),
        metadata.len(),
        metadata.modified()?.duration_since(UNIX_EPOCH)?.as_secs()
    );
    
    // Encrypt with AES-GCM
    let mut cipher = SecureAesGcm::new(key);
    let encrypted = cipher.encrypt_with_ad(&plaintext, ad.as_bytes())?;
    
    // Write encrypted file
    let enc_path = file_path.with_extension("enc");
    let mut file = File::create(&enc_path)?;
    
    // Write header
    file.write_all(b"AES256GCM")?;
    file.write_all(&encrypted.nonce)?;
    file.write_all(&encrypted.tag)?;
    file.write_all(&(ad.len() as u32).to_le_bytes())?;
    file.write_all(ad.as_bytes())?;
    file.write_all(&encrypted.ciphertext)?;
    
    // Securely delete original
    secure_delete(file_path)?;
    
    Ok(())
}

encrypt_ctr(key: bytes[32], plaintext: bytes, nonce: bytes[16]) -> bytes

Security Contract:

Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Confidentiality | ✅ | Stream cipher mode | | Authenticity | ❌ | No authentication | | Bit Flipping | ❌ | Malleable | | Nonce Reuse | ❌ | XOR reveals plaintext | | Known Plaintext | ✅ | Still secure |

Security Requirements:

Secure Usage Example:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import struct

class SecureAESCTR:
    def __init__(self, key: bytes):
        if len(key) != 32:
            raise ValueError("Key must be 32 bytes")
        self.key = key
        
    def encrypt_with_hmac(self, plaintext: bytes) -> dict:
        """CTR encryption with HMAC authentication"""
        
        # Generate random IV
        iv = os.urandom(16)
        
        # Encrypt using CTR mode
        cipher = Cipher(
            algorithms.AES(self.key),
            modes.CTR(iv),
            backend=default_backend()
        )
        encryptor = cipher.encryptor()
        ciphertext = encryptor.update(plaintext) + encryptor.finalize()
        
        # Add authentication
        hmac_key = hkdf_expand(self.key, b"hmac-key", 32)
        h = hmac.new(hmac_key, digestmod=hashlib.sha256)
        h.update(iv)
        h.update(ciphertext)
        tag = h.digest()
        
        # Clear derived key
        secure_zero(hmac_key)
        
        return {
            'ciphertext': ciphertext,
            'iv': iv,
            'tag': tag,
            'algorithm': 'AES-256-CTR-HMAC-SHA256'
        }
    
    def create_seekable_cipher(self, base_nonce: bytes) -> 'SeekableCipher':
        """Create cipher that can encrypt/decrypt at any position"""
        
        return self.SeekableCipher(self.key, base_nonce)
    
    class SeekableCipher:
        def __init__(self, key: bytes, base_nonce: bytes):
            self.key = key
            self.base_nonce = base_nonce[:12]  # 96-bit nonce
            
        def process_block(self, block_num: int, data: bytes) -> bytes:
            """Encrypt/decrypt specific block"""
            
            # Create counter for this block
            counter = self.base_nonce + struct.pack('>I', block_num)
            
            # Create cipher for this block
            cipher = Cipher(
                algorithms.AES(self.key),
                modes.CTR(counter),
                backend=default_backend()
            )
            processor = cipher.encryptor()  # Same for encrypt/decrypt
            
            return processor.update(data) + processor.finalize()

# SECURE: Stream encryption
def encrypt_stream(input_stream, output_stream, key: bytes):
    """Encrypt stream with CTR mode"""
    
    # Generate random nonce
    nonce = os.urandom(12)
    counter = 0
    
    # Write header
    output_stream.write(b'AES256CTR')
    output_stream.write(nonce)
    
    # Initialize HMAC
    hmac_key = hkdf_expand(key, b"stream-hmac", 32)
    h = hmac.new(hmac_key, digestmod=hashlib.sha256)
    h.update(nonce)
    
    # Process in chunks
    while True:
        chunk = input_stream.read(4096)
        if not chunk:
            break
        
        # Generate keystream for this chunk
        counter_bytes = nonce + counter.to_bytes(4, 'big')
        cipher = Cipher(
            algorithms.AES(key),
            modes.CTR(counter_bytes),
            backend=default_backend()
        )
        encryptor = cipher.encryptor()
        
        # Encrypt chunk
        encrypted_chunk = encryptor.update(chunk) + encryptor.finalize()
        
        # Update HMAC
        h.update(encrypted_chunk)
        
        # Write encrypted chunk
        output_stream.write(encrypted_chunk)
        
        counter += 1
    
    # Write final HMAC
    output_stream.write(h.digest())
    
    # Clear HMAC key
    secure_zero(hmac_key)

encrypt_cbc(key: bytes[32], plaintext: bytes, iv: bytes[16]) -> bytes

Security Contract:

Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Confidentiality | ✅ | When IV is random | | Authenticity | ❌ | No authentication | | Padding Oracle | ❌ | Without MAC | | IV Prediction | ❌ | If IV predictable | | Block Reordering | ❌ | Without MAC |

Security Requirements:

Secure Usage Example:

/// Secure CBC with encrypt-then-MAC
pub struct AesCbcMac {
    cipher_key: [u8; 32],
    mac_key: [u8; 32],
}

impl AesCbcMac {
    pub fn new(master_key: &[u8; 32]) -> Self {
        // Derive separate keys
        let cipher_key = hkdf_expand(master_key, b"aes-key", 32);
        let mac_key = hkdf_expand(master_key, b"mac-key", 32);
        
        Self {
            cipher_key: cipher_key.try_into().unwrap(),
            mac_key: mac_key.try_into().unwrap(),
        }
    }
    
    pub fn encrypt(&self, plaintext: &[u8]) -> Result<EncryptedMessage, Error> {
        // Generate random IV
        let mut iv = [0u8; 16];
        OsRng.fill_bytes(&mut iv);
        
        // Pad plaintext
        let padded = pkcs7_pad(plaintext, 16);
        
        // Encrypt
        let cipher = Aes256CbcEnc::new(&self.cipher_key.into(), &iv.into());
        let ciphertext = cipher.encrypt_padded_vec::<Pkcs7>(&padded)?;
        
        // Compute MAC over IV || ciphertext
        let mut mac_data = Vec::new();
        mac_data.extend_from_slice(&iv);
        mac_data.extend_from_slice(&ciphertext);
        
        let tag = hmac_sha256(&self.mac_key, &mac_data);
        
        Ok(EncryptedMessage {
            iv,
            ciphertext,
            tag,
        })
    }
    
    pub fn decrypt(&self, msg: &EncryptedMessage) -> Result<Vec<u8>, Error> {
        // Verify MAC first
        let mut mac_data = Vec::new();
        mac_data.extend_from_slice(&msg.iv);
        mac_data.extend_from_slice(&msg.ciphertext);
        
        let expected_tag = hmac_sha256(&self.mac_key, &mac_data);
        
        if !constant_time_eq(&expected_tag, &msg.tag) {
            return Err(Error::AuthenticationFailed);
        }
        
        // Only decrypt after MAC verification
        let cipher = Aes256CbcDec::new(&self.cipher_key.into(), &msg.iv.into());
        let padded = cipher.decrypt_padded_vec::<Pkcs7>(&msg.ciphertext)?;
        
        Ok(padded)
    }
}

/// Timing-safe padding validation
fn validate_pkcs7_padding(data: &[u8]) -> Result<usize, Error> {
    if data.is_empty() {
        return Err(Error::InvalidPadding);
    }
    
    let pad_byte = data[data.len() - 1];
    let pad_len = pad_byte as usize;
    
    if pad_len == 0 || pad_len > 16 || pad_len > data.len() {
        return Err(Error::InvalidPadding);
    }
    
    // Constant-time padding check
    let mut valid = 1u8;
    for i in 0..pad_len {
        let byte = data[data.len() - 1 - i];
        valid &= constant_time_eq_u8(byte, pad_byte);
    }
    
    if valid == 0 {
        return Err(Error::InvalidPadding);
    }
    
    Ok(data.len() - pad_len)
}

Security Best Practices

Mode Selection Guide

def select_aes_mode(use_case: str) -> dict:
    """Select appropriate AES mode for use case"""
    
    modes = {
        'general_encryption': {
            'mode': 'GCM',
            'reason': 'Authenticated encryption, best general choice',
            'implementation': 'AES-256-GCM'
        },
        'streaming_encryption': {
            'mode': 'CTR',
            'reason': 'No padding, seekable, add HMAC',
            'implementation': 'AES-256-CTR-HMAC'
        },
        'disk_encryption': {
            'mode': 'XTS',
            'reason': 'Designed for storage, no IV expansion',
            'implementation': 'AES-256-XTS'
        },
        'legacy_compatibility': {
            'mode': 'CBC',
            'reason': 'Wide support, needs encrypt-then-MAC',
            'implementation': 'AES-256-CBC-HMAC'
        },
        'key_wrapping': {
            'mode': 'KW/KWP',
            'reason': 'RFC 3394/5649 key wrapping',
            'implementation': 'AES-256-KW'
        }
    }
    
    if use_case not in modes:
        # Default to GCM
        return modes['general_encryption']
    
    return modes[use_case]

Secure Implementation Patterns

/// Secure AES implementation with all modes
pub struct SecureAES {
    key: [u8; 32],
    usage_counter: AtomicU64,
    created_at: Instant,
}

impl SecureAES {
    pub fn new() -> Result<Self, Error> {
        let mut key = [0u8; 32];
        OsRng.fill_bytes(&mut key);
        
        Ok(Self {
            key,
            usage_counter: AtomicU64::new(0),
            created_at: Instant::now(),
        })
    }
    
    pub fn should_rotate(&self) -> bool {
        // Check usage limits
        let usage = self.usage_counter.load(Ordering::Relaxed);
        if usage > 2u64.pow(32) {
            return true;
        }
        
        // Check age
        if self.created_at.elapsed() > Duration::from_secs(30 * 24 * 3600) {
            return true;
        }
        
        false
    }
    
    pub fn encrypt_authenticated(
        &self,
        plaintext: &[u8],
        associated_data: &[u8],
    ) -> Result<Vec<u8>, Error> {
        // Increment usage counter
        self.usage_counter.fetch_add(1, Ordering::Relaxed);
        
        // Use GCM for authenticated encryption
        let cipher = Aes256Gcm::new(Key::from_slice(&self.key));
        let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
        
        let payload = Payload {
            msg: plaintext,
            aad: associated_data,
        };
        
        let ciphertext = cipher.encrypt(&nonce, payload)?;
        
        // Format: nonce || ciphertext || tag
        let mut result = Vec::with_capacity(12 + ciphertext.len());
        result.extend_from_slice(&nonce);
        result.extend_from_slice(&ciphertext);
        
        Ok(result)
    }
}

impl Drop for SecureAES {
    fn drop(&mut self) {
        // Securely clear key on drop
        self.key.zeroize();
    }
}

Common Integration Patterns

Database Field Encryption

class FieldEncryption:
    def __init__(self, master_key: bytes):
        self.master_key = master_key
        
    def encrypt_field(self, table: str, column: str, value: bytes, row_id: int) -> dict:
        """Encrypt database field with deterministic IV for searchability"""
        
        # Derive table-specific key
        table_key = hkdf_expand(
            self.master_key,
            f"table:{table}".encode(),
            32
        )
        
        # For deterministic encryption (searchable)
        if column in ['email', 'ssn', 'phone']:
            # Derive IV from value for deterministic encryption
            iv = hkdf_expand(
                table_key,
                f"iv:{column}:{value}".encode(),
                16
            )
            
            # Use SIV mode for deterministic authenticated encryption
            return self._encrypt_siv(table_key, value, iv)
        
        # For randomized encryption
        else:
            return self._encrypt_gcm(table_key, value, row_id)

Envelope Encryption

/// Envelope encryption for large data
pub struct EnvelopeEncryption {
    kek: [u8; 32],  // Key encryption key
}

impl EnvelopeEncryption {
    pub fn encrypt_large_file(&self, file_path: &Path) -> Result<(), Error> {
        // Generate data encryption key
        let mut dek = [0u8; 32];
        OsRng.fill_bytes(&mut dek);
        
        // Encrypt DEK with KEK
        let wrapped_dek = aes_key_wrap(&self.kek, &dek)?;
        
        // Encrypt file with DEK
        let mut file = File::open(file_path)?;
        let mut encrypted = Vec::new();
        
        let cipher = Aes256Gcm::new(Key::from_slice(&dek));
        let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
        
        // Process in chunks
        let mut buffer = vec![0u8; 1024 * 1024];  // 1MB chunks
        loop {
            let n = file.read(&mut buffer)?;
            if n == 0 {
                break;
            }
            
            let chunk = cipher.encrypt(&nonce, &buffer[..n])?;
            encrypted.extend_from_slice(&chunk);
        }
        
        // Clear DEK
        dek.zeroize();
        
        // Write encrypted file with header
        let output = format!("{}.enc", file_path.display());
        let mut out = File::create(output)?;
        
        // Header: wrapped_dek || nonce || ciphertext
        out.write_all(&wrapped_dek)?;
        out.write_all(&nonce)?;
        out.write_all(&encrypted)?;
        
        Ok(())
    }
}

Performance Considerations

Mode Speed (AES-NI) Speed (Software) Notes
ECB 0.6 cycles/byte 10 cycles/byte Never use
CBC Encrypt 3.0 cycles/byte 15 cycles/byte Sequential
CBC Decrypt 0.7 cycles/byte 15 cycles/byte Parallel
CTR 0.7 cycles/byte 11 cycles/byte Parallel
GCM 1.0 cycles/byte 20 cycles/byte Auth included
XTS 1.5 cycles/byte 25 cycles/byte Storage

Hardware Acceleration

Security Auditing

Verification Checklist

Common Vulnerabilities

# AUDIT: Look for these patterns

# ❌ ECB mode
cipher = AES.new(key, AES.MODE_ECB)  # NEVER!

# ❌ Static IV
iv = b'\x00' * 16  # Breaks security!

# ❌ No authentication
ciphertext = encrypt_cbc(key, plaintext, iv)  # Malleable!

# ❌ Predictable IV
iv = struct.pack('<Q', counter) + b'\x00' * 8  # Predictable!

# ❌ Hardcoded keys
KEY = b'0123456789abcdef' * 2  # In source code!

Security Analysis

Threat Model: AES-256 Threat Model

The comprehensive threat analysis covers:

For complete security analysis and risk assessment, see the dedicated threat model documentation.

References

  1. NIST FIPS 197 - AES Specification
  2. NIST SP 800-38A - Block Cipher Modes
  3. NIST SP 800-38D - GCM Mode

Support

Security Issues: security@metamui.id
Documentation Updates: phantom@metamui.id
Vulnerability Disclosure: See SECURITY.md


Document Version: 1.0
Review Cycle: Quarterly
Next Review: 2025-04-05
Classification: PUBLIC