Encryption Implementation
Version: 1.0 Date: October 08, 2025 SPDX-License-Identifier: BSD-3-Clause License File: See the LICENSE file in the project root. Copyright: © 2025 Michael Gardner, A Bit of Help, Inc. Authors: Michael Gardner, Claude Code Status: Active
Overview
The encryption service provides authenticated encryption with multiple algorithms, secure key management, and automatic integrity verification. It's implemented as an infrastructure adapter that implements the domain's EncryptionService
trait.
File: pipeline/src/infrastructure/adapters/encryption_service_adapter.rs
Supported Algorithms
AES-256-GCM (Advanced Encryption Standard)
- Key size: 256 bits (32 bytes)
- Nonce size: 96 bits (12 bytes)
- Security: Industry standard, FIPS 140-2 approved
- Performance: Excellent with AES-NI hardware acceleration
- Library:
aes-gcm
crate
Best for:
- Compliance requirements (FIPS, government)
- Systems with AES-NI support
- Maximum security requirements
- Long-term data protection
Performance characteristics:
Operation | With AES-NI | Without AES-NI
------------|-------------|----------------
Encryption | 2-3 GB/s | 100-200 MB/s
Decryption | 2-3 GB/s | 100-200 MB/s
Key setup | < 1 μs | < 1 μs
Memory | Low | Low
ChaCha20-Poly1305 (Stream Cipher)
- Key size: 256 bits (32 bytes)
- Nonce size: 96 bits (12 bytes)
- Security: Modern, constant-time implementation
- Performance: Consistent across all platforms
- Library:
chacha20poly1305
crate
Best for:
- Systems without AES-NI
- Mobile/embedded devices
- Constant-time requirements
- Side-channel attack resistance
Performance characteristics:
Operation | Any Platform
------------|-------------
Encryption | 500-800 MB/s
Decryption | 500-800 MB/s
Key setup | < 1 μs
Memory | Low
AES-128-GCM (Faster AES Variant)
- Key size: 128 bits (16 bytes)
- Nonce size: 96 bits (12 bytes)
- Security: Very secure, faster than AES-256
- Performance: ~30% faster than AES-256
- Library:
aes-gcm
crate
Best for:
- High-performance requirements
- Short-term data protection
- Real-time encryption
- Bandwidth-constrained systems
Performance characteristics:
Operation | With AES-NI | Without AES-NI
------------|-------------|----------------
Encryption | 3-4 GB/s | 150-250 MB/s
Decryption | 3-4 GB/s | 150-250 MB/s
Key setup | < 1 μs | < 1 μs
Memory | Low | Low
Security Features
Authenticated Encryption (AEAD)
All algorithms provide Authenticated Encryption with Associated Data (AEAD):
Plaintext → Encrypt → Ciphertext + Authentication Tag
↓
Detects tampering
Properties:
- Confidentiality: Data is encrypted and unreadable
- Integrity: Any modification is detected
- Authentication: Verifies data origin
Nonce Management
Each encryption operation requires a unique nonce (number used once):
#![allow(unused)] fn main() { // Nonces are automatically generated for each chunk pub struct EncryptionContext { key: SecretKey, nonce_counter: AtomicU64, // Incrementing counter } impl EncryptionContext { fn next_nonce(&self) -> Nonce { let counter = self.nonce_counter.fetch_add(1, Ordering::SeqCst); // Generate nonce from counter let mut nonce = [0u8; 12]; nonce[0..8].copy_from_slice(&counter.to_le_bytes()); Nonce::from_slice(&nonce) } } }
Important: Never reuse a nonce with the same key!
Key Derivation
Derive encryption keys from passwords using secure KDFs:
#![allow(unused)] fn main() { pub enum KeyDerivationFunction { Argon2, // Memory-hard, GPU-resistant Scrypt, // Memory-hard, tunable PBKDF2, // Standard, widely supported } // Derive key from password pub fn derive_key( password: &[u8], salt: &[u8], kdf: KeyDerivationFunction, ) -> Result<SecretKey, PipelineError> { match kdf { KeyDerivationFunction::Argon2 => { // Memory: 64 MB, Iterations: 3, Parallelism: 4 argon2::hash_raw(password, salt, &argon2::Config::default()) } KeyDerivationFunction::Scrypt => { // N=16384, r=8, p=1 scrypt::scrypt(password, salt, &scrypt::Params::new(14, 8, 1)?) } KeyDerivationFunction::PBKDF2 => { // 100,000 iterations pbkdf2::pbkdf2_hmac::<sha2::Sha256>(password, salt, 100_000) } } } }
Architecture
Service Interface (Domain Layer)
#![allow(unused)] fn main() { // pipeline-domain/src/services/encryption_service.rs use async_trait::async_trait; use crate::value_objects::Algorithm; use crate::error::PipelineError; #[async_trait] pub trait EncryptionService: Send + Sync { /// Encrypt data using the specified algorithm async fn encrypt( &self, data: &[u8], algorithm: &Algorithm, key: &EncryptionKey, ) -> Result<EncryptedData, PipelineError>; /// Decrypt data using the specified algorithm async fn decrypt( &self, encrypted: &EncryptedData, algorithm: &Algorithm, key: &EncryptionKey, ) -> Result<Vec<u8>, PipelineError>; } /// Encrypted data with nonce and authentication tag pub struct EncryptedData { pub ciphertext: Vec<u8>, pub nonce: Vec<u8>, // 12 bytes pub tag: Vec<u8>, // 16 bytes (authentication tag) } }
Service Implementation (Infrastructure Layer)
#![allow(unused)] fn main() { // pipeline/src/infrastructure/adapters/encryption_service_adapter.rs use aes_gcm::{Aes256Gcm, Key, Nonce}; use aes_gcm::aead::{Aead, NewAead}; use chacha20poly1305::ChaCha20Poly1305; pub struct EncryptionServiceAdapter { // Secure key storage key_store: Arc<RwLock<KeyStore>>, } #[async_trait] impl EncryptionService for EncryptionServiceAdapter { async fn encrypt( &self, data: &[u8], algorithm: &Algorithm, key: &EncryptionKey, ) -> Result<EncryptedData, PipelineError> { match algorithm.name() { "aes-256-gcm" => self.encrypt_aes_256_gcm(data, key), "chacha20-poly1305" => self.encrypt_chacha20(data, key), "aes-128-gcm" => self.encrypt_aes_128_gcm(data, key), _ => Err(PipelineError::UnsupportedAlgorithm( algorithm.name().to_string() )), } } async fn decrypt( &self, encrypted: &EncryptedData, algorithm: &Algorithm, key: &EncryptionKey, ) -> Result<Vec<u8>, PipelineError> { match algorithm.name() { "aes-256-gcm" => self.decrypt_aes_256_gcm(encrypted, key), "chacha20-poly1305" => self.decrypt_chacha20(encrypted, key), "aes-128-gcm" => self.decrypt_aes_128_gcm(encrypted, key), _ => Err(PipelineError::UnsupportedAlgorithm( algorithm.name().to_string() )), } } } }
Algorithm Implementations
AES-256-GCM Implementation
#![allow(unused)] fn main() { impl EncryptionServiceAdapter { fn encrypt_aes_256_gcm( &self, data: &[u8], key: &EncryptionKey, ) -> Result<EncryptedData, PipelineError> { use aes_gcm::{Aes256Gcm, Key, Nonce}; use aes_gcm::aead::{Aead, NewAead}; // Create cipher from key let key = Key::from_slice(key.as_bytes()); let cipher = Aes256Gcm::new(key); // Generate unique nonce let nonce = self.generate_nonce(); let nonce_obj = Nonce::from_slice(&nonce); // Encrypt with authentication let ciphertext = cipher .encrypt(nonce_obj, data) .map_err(|e| PipelineError::EncryptionError(e.to_string()))?; // Split ciphertext and tag let (ciphertext_bytes, tag) = ciphertext.split_at(ciphertext.len() - 16); Ok(EncryptedData { ciphertext: ciphertext_bytes.to_vec(), nonce: nonce.to_vec(), tag: tag.to_vec(), }) } fn decrypt_aes_256_gcm( &self, encrypted: &EncryptedData, key: &EncryptionKey, ) -> Result<Vec<u8>, PipelineError> { use aes_gcm::{Aes256Gcm, Key, Nonce}; use aes_gcm::aead::{Aead, NewAead}; // Create cipher from key let key = Key::from_slice(key.as_bytes()); let cipher = Aes256Gcm::new(key); // Reconstruct nonce let nonce = Nonce::from_slice(&encrypted.nonce); // Combine ciphertext and tag let mut combined = encrypted.ciphertext.clone(); combined.extend_from_slice(&encrypted.tag); // Decrypt and verify authentication let plaintext = cipher .decrypt(nonce, combined.as_slice()) .map_err(|e| PipelineError::DecryptionError( format!("Decryption failed (possibly tampered): {}", e) ))?; Ok(plaintext) } } }
ChaCha20-Poly1305 Implementation
#![allow(unused)] fn main() { impl EncryptionServiceAdapter { fn encrypt_chacha20( &self, data: &[u8], key: &EncryptionKey, ) -> Result<EncryptedData, PipelineError> { use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; use chacha20poly1305::aead::{Aead, NewAead}; // Create cipher from key let key = Key::from_slice(key.as_bytes()); let cipher = ChaCha20Poly1305::new(key); // Generate unique nonce let nonce = self.generate_nonce(); let nonce_obj = Nonce::from_slice(&nonce); // Encrypt with authentication let ciphertext = cipher .encrypt(nonce_obj, data) .map_err(|e| PipelineError::EncryptionError(e.to_string()))?; // Split ciphertext and tag let (ciphertext_bytes, tag) = ciphertext.split_at(ciphertext.len() - 16); Ok(EncryptedData { ciphertext: ciphertext_bytes.to_vec(), nonce: nonce.to_vec(), tag: tag.to_vec(), }) } fn decrypt_chacha20( &self, encrypted: &EncryptedData, key: &EncryptionKey, ) -> Result<Vec<u8>, PipelineError> { use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; use chacha20poly1305::aead::{Aead, NewAead}; // Create cipher from key let key = Key::from_slice(key.as_bytes()); let cipher = ChaCha20Poly1305::new(key); // Reconstruct nonce let nonce = Nonce::from_slice(&encrypted.nonce); // Combine ciphertext and tag let mut combined = encrypted.ciphertext.clone(); combined.extend_from_slice(&encrypted.tag); // Decrypt and verify authentication let plaintext = cipher .decrypt(nonce, combined.as_slice()) .map_err(|e| PipelineError::DecryptionError( format!("Decryption failed (possibly tampered): {}", e) ))?; Ok(plaintext) } } }
Key Management
Secure Key Storage
#![allow(unused)] fn main() { use zeroize::Zeroize; /// Secure key that zeroizes on drop pub struct SecretKey { bytes: Vec<u8>, } impl SecretKey { pub fn new(bytes: Vec<u8>) -> Self { Self { bytes } } pub fn as_bytes(&self) -> &[u8] { &self.bytes } /// Generate random key pub fn generate(size: usize) -> Self { use rand::RngCore; let mut bytes = vec![0u8; size]; rand::thread_rng().fill_bytes(&mut bytes); Self::new(bytes) } } impl Drop for SecretKey { fn drop(&mut self) { // Securely wipe key from memory self.bytes.zeroize(); } } impl Zeroize for SecretKey { fn zeroize(&mut self) { self.bytes.zeroize(); } } }
Key Rotation
#![allow(unused)] fn main() { pub struct KeyRotation { current_key: SecretKey, previous_key: Option<SecretKey>, rotation_interval: Duration, last_rotation: Instant, } impl KeyRotation { pub fn rotate(&mut self) -> Result<(), PipelineError> { // Save current key as previous let old_key = std::mem::replace( &mut self.current_key, SecretKey::generate(32), ); self.previous_key = Some(old_key); self.last_rotation = Instant::now(); Ok(()) } pub fn should_rotate(&self) -> bool { self.last_rotation.elapsed() >= self.rotation_interval } } }
Performance Optimizations
Parallel Chunk Encryption
#![allow(unused)] fn main() { use rayon::prelude::*; pub async fn encrypt_chunks( chunks: Vec<FileChunk>, algorithm: &Algorithm, key: &SecretKey, encryption_service: &Arc<dyn EncryptionService>, ) -> Result<Vec<EncryptedChunk>, PipelineError> { // Encrypt chunks in parallel chunks.par_iter() .map(|chunk| { let encrypted = encryption_service .encrypt(&chunk.data, algorithm, key)?; Ok(EncryptedChunk { sequence: chunk.sequence, data: encrypted, original_size: chunk.data.len(), }) }) .collect() } }
Hardware Acceleration
#![allow(unused)] fn main() { // Detect AES-NI support pub fn has_aes_ni() -> bool { #[cfg(target_arch = "x86_64")] { use std::arch::x86_64::*; is_x86_feature_detected!("aes") } #[cfg(not(target_arch = "x86_64"))] { false } } // Select algorithm based on hardware pub fn select_algorithm() -> Algorithm { if has_aes_ni() { Algorithm::aes_256_gcm() // Fast with hardware support } else { Algorithm::chacha20_poly1305() // Consistent without hardware } } }
Configuration
Encryption Configuration
#![allow(unused)] fn main() { pub struct EncryptionConfig { pub algorithm: Algorithm, pub key_derivation: KeyDerivationFunction, pub key_rotation_interval: Duration, pub nonce_reuse_prevention: bool, } impl EncryptionConfig { pub fn maximum_security() -> Self { Self { algorithm: Algorithm::aes_256_gcm(), key_derivation: KeyDerivationFunction::Argon2, key_rotation_interval: Duration::from_secs(86400), // 24 hours nonce_reuse_prevention: true, } } pub fn balanced() -> Self { Self { algorithm: if has_aes_ni() { Algorithm::aes_256_gcm() } else { Algorithm::chacha20_poly1305() }, key_derivation: KeyDerivationFunction::Scrypt, key_rotation_interval: Duration::from_secs(604800), // 7 days nonce_reuse_prevention: true, } } pub fn high_performance() -> Self { Self { algorithm: if has_aes_ni() { Algorithm::aes_128_gcm() } else { Algorithm::chacha20_poly1305() }, key_derivation: KeyDerivationFunction::PBKDF2, key_rotation_interval: Duration::from_secs(2592000), // 30 days nonce_reuse_prevention: true, } } } }
Error Handling
#![allow(unused)] fn main() { #[derive(Debug, thiserror::Error)] pub enum EncryptionError { #[error("Encryption failed: {0}")] EncryptionFailed(String), #[error("Decryption failed: {0}")] DecryptionFailed(String), #[error("Authentication failed - data may be tampered")] AuthenticationFailed, #[error("Invalid key length: expected {expected}, got {actual}")] InvalidKeyLength { expected: usize, actual: usize }, #[error("Nonce reuse detected")] NonceReuse, #[error("Key derivation failed: {0}")] KeyDerivationFailed(String), } impl From<EncryptionError> for PipelineError { fn from(err: EncryptionError) -> Self { match err { EncryptionError::EncryptionFailed(msg) => PipelineError::EncryptionError(msg), EncryptionError::DecryptionFailed(msg) => PipelineError::DecryptionError(msg), EncryptionError::AuthenticationFailed => PipelineError::IntegrityError("Authentication failed".to_string()), _ => PipelineError::EncryptionError(err.to_string()), } } } }
Usage Examples
Basic Encryption
use adaptive_pipeline::infrastructure::adapters::EncryptionServiceAdapter; use adaptive_pipeline_domain::services::EncryptionService; use adaptive_pipeline_domain::value_objects::Algorithm; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // Create encryption service let encryption = EncryptionServiceAdapter::new(); // Generate encryption key let key = SecretKey::generate(32); // 256 bits // Encrypt data let data = b"Sensitive information"; let encrypted = encryption.encrypt( data, &Algorithm::aes_256_gcm(), &key ).await?; println!("Original size: {} bytes", data.len()); println!("Encrypted size: {} bytes", encrypted.ciphertext.len()); println!("Nonce: {} bytes", encrypted.nonce.len()); println!("Tag: {} bytes", encrypted.tag.len()); // Decrypt data let decrypted = encryption.decrypt( &encrypted, &Algorithm::aes_256_gcm(), &key ).await?; assert_eq!(data, decrypted.as_slice()); println!("✓ Decryption successful"); Ok(()) }
Password-Based Encryption
#![allow(unused)] fn main() { async fn encrypt_with_password( data: &[u8], password: &str, ) -> Result<EncryptedData, PipelineError> { // Generate random salt let salt = SecretKey::generate(16); // Derive key from password let key = derive_key( password.as_bytes(), salt.as_bytes(), KeyDerivationFunction::Argon2, )?; // Encrypt data let encryption = EncryptionServiceAdapter::new(); let encrypted = encryption.encrypt( data, &Algorithm::aes_256_gcm(), &key, ).await?; // Store salt with encrypted data encrypted.salt = salt.as_bytes().to_vec(); Ok(encrypted) } }
Tamper Detection
#![allow(unused)] fn main() { async fn decrypt_with_verification( encrypted: &EncryptedData, key: &SecretKey, ) -> Result<Vec<u8>, PipelineError> { let encryption = EncryptionServiceAdapter::new(); // Attempt decryption (will fail if tampered) match encryption.decrypt(encrypted, &Algorithm::aes_256_gcm(), key).await { Ok(plaintext) => { println!("✓ Data is authentic and unmodified"); Ok(plaintext) } Err(PipelineError::DecryptionError(_)) => { eprintln!("✗ Data has been tampered with!"); Err(PipelineError::IntegrityError( "Authentication failed - data may be tampered".to_string() )) } Err(e) => Err(e), } } }
Benchmarks
Typical performance on modern systems:
Algorithm | File Size | Encrypt Time | Decrypt Time | Throughput
-------------------|-----------|--------------|--------------|------------
AES-256-GCM (NI) | 100 MB | 0.04s | 0.04s | 2.5 GB/s
AES-256-GCM (SW) | 100 MB | 0.8s | 0.8s | 125 MB/s
ChaCha20-Poly1305 | 100 MB | 0.15s | 0.15s | 670 MB/s
AES-128-GCM (NI) | 100 MB | 0.03s | 0.03s | 3.3 GB/s
Best Practices
Algorithm Selection
Use AES-256-GCM when:
- Compliance requires FIPS-approved encryption
- Long-term data protection is needed
- Hardware has AES-NI support
- Maximum security is required
Use ChaCha20-Poly1305 when:
- Running on platforms without AES-NI
- Constant-time execution is critical
- Side-channel resistance is needed
- Mobile/embedded deployment
Use AES-128-GCM when:
- Maximum performance is required
- Short-term data protection is sufficient
- Hardware has AES-NI support
Key Management
#![allow(unused)] fn main() { // ✅ GOOD: Secure key handling let key = SecretKey::generate(32); let encrypted = encrypt(data, &key)?; // Key is automatically zeroized on drop // ❌ BAD: Exposing key in logs println!("Key: {:?}", key); // Never log keys! // ✅ GOOD: Key derivation from password let key = derive_key(password, salt, KeyDerivationFunction::Argon2)?; // ❌ BAD: Weak key derivation let key = sha256(password); // Not secure! }
Nonce Management
#![allow(unused)] fn main() { // ✅ GOOD: Unique nonce per encryption let nonce = generate_unique_nonce(); // ❌ BAD: Reusing nonces let nonce = [0u8; 12]; // NEVER reuse nonces! // ✅ GOOD: Counter-based nonces let nonce_counter = AtomicU64::new(0); let nonce = generate_nonce_from_counter(nonce_counter.fetch_add(1)); }
Authentication Verification
#![allow(unused)] fn main() { // ✅ GOOD: Always verify authentication match decrypt(encrypted, key) { Ok(data) => process(data), Err(e) => { log::error!("Decryption failed - possible tampering"); return Err(e); } } // ❌ BAD: Ignoring authentication failures let data = decrypt(encrypted, key).unwrap_or_default(); // Dangerous! }
Security Considerations
Nonce Uniqueness
- Critical: Never reuse a nonce with the same key
- Use counter-based or random nonces
- Rotate keys after 2^32 encryptions (GCM limit)
Key Strength
- Minimum 256 bits for long-term security
- Use cryptographically secure random number generators
- Derive keys properly from passwords (use Argon2)
Memory Security
- Keys are automatically zeroized on drop
- Avoid cloning keys unnecessarily
- Don't log or print keys
Side-Channel Attacks
- ChaCha20 provides constant-time execution
- AES requires AES-NI for timing attack resistance
- Validate all inputs before decryption
Next Steps
Now that you understand encryption implementation:
- Integrity Verification - Checksum and hashing
- Key Management - Advanced key handling
- Security Best Practices - Comprehensive security guide
- Compression - Data compression before encryption