use crate::crypto::{
CryptoError, CryptoResult, DerivedKey, EncryptedSecret, PlaintextSecret,
SecretMetadata, SecretType, defaults, keys::SecureRandom
};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce, aead::{Aead, KeyInit}};
use argon2::Argon2;
use std::time::{SystemTime, UNIX_EPOCH};
use zeroize::Zeroize;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct CryptoEngine {
performance_profile: PerformanceProfile,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum PerformanceProfile {
Fast,
Balanced,
Secure,
Paranoid,
}
impl Default for PerformanceProfile {
fn default() -> Self {
Self::Balanced
}
}
impl PerformanceProfile {
pub fn argon2_params(&self) -> argon2::Params {
match self {
Self::Fast => argon2::Params::new(
4096, 1, 1, Some(32),
).expect("Valid Argon2 params"),
Self::Balanced => argon2::Params::new(
65536, 3, 4, Some(32),
).expect("Valid Argon2 params"),
Self::Secure => argon2::Params::new(
262144, 5, 8, Some(32),
).expect("Valid Argon2 params"),
Self::Paranoid => argon2::Params::new(
1048576, 10, 16, Some(32),
).expect("Valid Argon2 params"),
}
}
}
#[derive(Debug, Clone)]
pub struct EncryptionOptions {
pub metadata: Option<SecretMetadata>,
pub performance_profile: Option<PerformanceProfile>,
pub salt: Option<[u8; defaults::SALT_LENGTH]>,
}
impl Default for EncryptionOptions {
fn default() -> Self {
Self {
metadata: None,
performance_profile: None,
salt: None,
}
}
}
impl EncryptionOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_metadata(mut self, metadata: SecretMetadata) -> Self {
self.metadata = Some(metadata);
self
}
pub fn with_performance_profile(mut self, profile: PerformanceProfile) -> Self {
self.performance_profile = Some(profile);
self
}
pub fn with_salt(mut self, salt: [u8; defaults::SALT_LENGTH]) -> Self {
self.salt = Some(salt);
self
}
pub fn with_description<S: Into<String>>(mut self, description: S) -> Self {
let mut metadata = self.metadata.unwrap_or_default();
metadata.description = Some(description.into());
self.metadata = Some(metadata);
self
}
pub fn with_type(mut self, secret_type: SecretType) -> Self {
let mut metadata = self.metadata.unwrap_or_default();
metadata.secret_type = Some(secret_type);
self.metadata = Some(metadata);
self
}
}
#[derive(Debug)]
pub struct BatchEncryptionResult {
pub successes: Vec<(String, EncryptedSecret)>,
pub failures: Vec<(String, CryptoError)>,
}
impl CryptoEngine {
pub fn new() -> Self {
Self {
performance_profile: PerformanceProfile::default(),
}
}
pub fn with_performance_profile(profile: PerformanceProfile) -> Self {
Self {
performance_profile: profile,
}
}
pub fn performance_profile(&self) -> PerformanceProfile {
self.performance_profile
}
pub fn encrypt_data(&self, data: &[u8], password: &str) -> CryptoResult<EncryptedSecret> {
self.encrypt_bytes(data, password, EncryptionOptions::default())
}
pub fn decrypt_data(&self, encrypted: &EncryptedSecret, password: &str) -> CryptoResult<Vec<u8>> {
self.decrypt_to_bytes(encrypted, password)
}
pub fn set_performance_profile(&mut self, profile: PerformanceProfile) {
self.performance_profile = profile;
}
pub fn generate_key(&self) -> CryptoResult<DerivedKey> {
let salt = SecureRandom::generate_salt()?;
let password = SecureRandom::generate_password(32)?;
self.derive_key(&password, &salt)
}
pub fn derive_key(&self, password: &str, salt: &[u8]) -> CryptoResult<DerivedKey> {
if salt.len() != defaults::SALT_LENGTH {
return Err(CryptoError::InvalidSalt {
reason: format!("Salt must be {} bytes, got {}", defaults::SALT_LENGTH, salt.len())
});
}
let mut salt_array = [0u8; defaults::SALT_LENGTH];
salt_array.copy_from_slice(salt);
self.derive_key_with_profile(password, &salt_array, self.performance_profile)
}
pub fn encrypt_string(
&self,
plaintext: &str,
password: &str,
options: EncryptionOptions,
) -> CryptoResult<EncryptedSecret> {
let secret = PlaintextSecret::from_string(plaintext.to_string());
self.encrypt(secret, password, options)
}
pub fn encrypt_bytes(
&self,
plaintext: &[u8],
password: &str,
options: EncryptionOptions,
) -> CryptoResult<EncryptedSecret> {
let secret = PlaintextSecret::from_bytes(plaintext.to_vec());
self.encrypt(secret, password, options)
}
pub fn encrypt(
&self,
plaintext: PlaintextSecret,
password: &str,
options: EncryptionOptions,
) -> CryptoResult<EncryptedSecret> {
let profile = options.performance_profile.unwrap_or(self.performance_profile);
let salt = match options.salt {
Some(salt) => salt,
None => SecureRandom::generate_salt()?,
};
let key = self.derive_key_with_profile(password, &salt, profile)?;
let metadata = options.metadata.or_else(|| {
let mut meta = SecretMetadata::new();
meta.created_at = Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
);
Some(meta)
});
EncryptedSecret::encrypt_with_key(plaintext, &key, metadata)
}
pub fn decrypt(&self, encrypted: &EncryptedSecret, password: &str) -> CryptoResult<PlaintextSecret> {
encrypted.decrypt_with_password(password)
}
pub fn decrypt_to_string(&self, encrypted: &EncryptedSecret, password: &str) -> CryptoResult<String> {
let plaintext = self.decrypt(encrypted, password)?;
plaintext.into_string()
}
pub fn decrypt_to_bytes(&self, encrypted: &EncryptedSecret, password: &str) -> CryptoResult<Vec<u8>> {
let plaintext = self.decrypt(encrypted, password)?;
Ok(plaintext.as_bytes().to_vec())
}
pub fn verify_password(&self, encrypted: &EncryptedSecret, password: &str) -> bool {
encrypted.verify_password(password)
}
pub fn change_password(
&self,
encrypted: &EncryptedSecret,
old_password: &str,
new_password: &str,
) -> CryptoResult<EncryptedSecret> {
encrypted.reencrypt_with_password(old_password, new_password)
}
pub fn encrypt_batch<I, S>(
&self,
secrets: I,
password: &str,
base_options: EncryptionOptions,
) -> BatchEncryptionResult
where
I: IntoIterator<Item = (String, S)>,
S: AsRef<str>,
{
let mut successes = Vec::new();
let mut failures = Vec::new();
for (name, secret_data) in secrets {
let options = base_options.clone();
match self.encrypt_string(secret_data.as_ref(), password, options) {
Ok(encrypted) => successes.push((name, encrypted)),
Err(error) => failures.push((name, error)),
}
}
BatchEncryptionResult { successes, failures }
}
pub fn encrypt_direct(
&self,
plaintext: &[u8],
key: &Key,
nonce: &[u8; defaults::NONCE_LENGTH],
) -> CryptoResult<Vec<u8>> {
let cipher = ChaCha20Poly1305::new(key);
let nonce_obj = Nonce::from_slice(nonce);
cipher
.encrypt(nonce_obj, plaintext)
.map_err(CryptoError::from)
}
pub fn decrypt_direct(
&self,
ciphertext: &[u8],
key: &Key,
nonce: &[u8; defaults::NONCE_LENGTH],
) -> CryptoResult<Vec<u8>> {
let cipher = ChaCha20Poly1305::new(key);
let nonce_obj = Nonce::from_slice(nonce);
cipher
.decrypt(nonce_obj, ciphertext)
.map_err(|_| CryptoError::AuthenticationFailed)
}
fn derive_key_with_profile(
&self,
password: &str,
salt: &[u8; defaults::SALT_LENGTH],
profile: PerformanceProfile,
) -> CryptoResult<DerivedKey> {
let params = profile.argon2_params();
let argon2 = Argon2::new(
defaults::ARGON2_ALGORITHM,
defaults::ARGON2_VERSION,
params,
);
let mut key_bytes = [0u8; defaults::KEY_LENGTH];
argon2
.hash_password_into(password.as_bytes(), salt, &mut key_bytes)
.map_err(CryptoError::from)?;
let key = Key::from_slice(&key_bytes).clone();
key_bytes.zeroize();
Ok(DerivedKey::from_password_with_salt(password, salt)?)
}
pub fn derive_key_with_salt(
&self,
password: &str,
salt: &[u8; defaults::SALT_LENGTH],
) -> CryptoResult<DerivedKey> {
self.derive_key_with_profile(password, salt, self.performance_profile)
}
pub fn generate_random_key() -> CryptoResult<Key> {
let key_bytes = SecureRandom::generate_bytes(defaults::KEY_LENGTH)?;
Ok(*Key::from_slice(&key_bytes))
}
pub fn generate_nonce() -> CryptoResult<[u8; defaults::NONCE_LENGTH]> {
SecureRandom::generate_nonce()
}
pub fn generate_salt() -> CryptoResult<[u8; defaults::SALT_LENGTH]> {
SecureRandom::generate_salt()
}
pub fn benchmark_performance(&self) -> CryptoResult<PerformanceBenchmark> {
let test_password = "benchmark_password_12345";
let test_data = "This is test data for benchmarking purposes. It contains enough text to provide meaningful encryption benchmarks.";
let start_time = std::time::Instant::now();
let derive_start = std::time::Instant::now();
let salt = Self::generate_salt()?;
let key = self.derive_key_with_profile(test_password, &salt, self.performance_profile)?;
let derive_duration = derive_start.elapsed();
let encrypt_start = std::time::Instant::now();
let plaintext = PlaintextSecret::from_string(test_data.to_string());
let encrypted = EncryptedSecret::encrypt_with_key(plaintext, &key, None)?;
let encrypt_duration = encrypt_start.elapsed();
let decrypt_start = std::time::Instant::now();
let _decrypted = encrypted.decrypt_with_key(&key)?;
let decrypt_duration = decrypt_start.elapsed();
let total_duration = start_time.elapsed();
Ok(PerformanceBenchmark {
profile: self.performance_profile,
key_derivation_ms: derive_duration.as_millis() as f64,
encryption_ms: encrypt_duration.as_micros() as f64 / 1000.0,
decryption_ms: decrypt_duration.as_micros() as f64 / 1000.0,
total_ms: total_duration.as_millis() as f64,
data_size: test_data.len(),
})
}
pub fn encrypt_file<P: AsRef<std::path::Path>>(
&self,
file_path: P,
password: &str,
salt: Option<&[u8; defaults::SALT_LENGTH]>,
) -> CryptoResult<EncryptedSecret> {
let content = std::fs::read_to_string(file_path)
.map_err(|e| CryptoError::Generic { message: format!("Failed to read file: {}", e) })?;
let plaintext = PlaintextSecret::from_string(content);
let derived_key = if let Some(salt) = salt {
self.derive_key_with_profile(password, salt, self.performance_profile)?
} else {
let salt = Self::generate_salt()?;
self.derive_key(password, &salt)?
};
EncryptedSecret::encrypt_with_key(plaintext, &derived_key, None)
}
pub fn decrypt_file(
&self,
encrypted: &EncryptedSecret,
password: &str,
) -> CryptoResult<String> {
let plaintext = encrypted.decrypt_with_password(password)?;
plaintext.into_string()
}
}
impl Default for CryptoEngine {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PerformanceBenchmark {
pub profile: PerformanceProfile,
pub key_derivation_ms: f64,
pub encryption_ms: f64,
pub decryption_ms: f64,
pub total_ms: f64,
pub data_size: usize,
}
impl PerformanceBenchmark {
pub fn encryption_throughput_mbps(&self) -> f64 {
if self.encryption_ms == 0.0 {
return 0.0;
}
(self.data_size as f64 / 1_048_576.0) / (self.encryption_ms / 1000.0)
}
pub fn decryption_throughput_mbps(&self) -> f64 {
if self.decryption_ms == 0.0 {
return 0.0;
}
(self.data_size as f64 / 1_048_576.0) / (self.decryption_ms / 1000.0)
}
pub fn meets_performance_target(&self) -> bool {
self.encryption_ms < 1.0 && self.decryption_ms < 1.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crypto_engine_basic_operations() {
let engine = CryptoEngine::new();
let plaintext = "Hello, World!";
let password = "test_password";
let encrypted = engine.encrypt_string(plaintext, password, EncryptionOptions::new()).unwrap();
let decrypted = engine.decrypt_to_string(&encrypted, password).unwrap();
assert_eq!(plaintext, decrypted);
}
#[test]
fn test_performance_profiles() {
let profiles = [
PerformanceProfile::Fast,
PerformanceProfile::Balanced,
PerformanceProfile::Secure,
PerformanceProfile::Paranoid,
];
for profile in profiles {
let engine = CryptoEngine::with_performance_profile(profile);
let plaintext = "Test data";
let password = "test_password";
let encrypted = engine.encrypt_string(plaintext, password, EncryptionOptions::new()).unwrap();
let decrypted = engine.decrypt_to_string(&encrypted, password).unwrap();
assert_eq!(plaintext, decrypted);
}
}
#[test]
fn test_batch_encryption() {
let engine = CryptoEngine::new();
let secrets = vec![
("api_key".to_string(), "sk-1234567890"),
("db_password".to_string(), "super_secret_db_pass"),
("jwt_secret".to_string(), "jwt-signing-key-12345"),
];
let password = "master_password";
let result = engine.encrypt_batch(secrets, password, EncryptionOptions::new());
assert_eq!(result.successes.len(), 3);
assert_eq!(result.failures.len(), 0);
for (name, encrypted) in result.successes {
let decrypted = engine.decrypt_to_string(&encrypted, password).unwrap();
assert!(!decrypted.is_empty());
println!("Decrypted {}: [REDACTED {} chars]", name, decrypted.len());
}
}
#[test]
fn test_password_change() {
let engine = CryptoEngine::new();
let plaintext = "Secret data";
let old_password = "old_password";
let new_password = "new_password";
let encrypted = engine.encrypt_string(plaintext, old_password, EncryptionOptions::new()).unwrap();
let reencrypted = engine.change_password(&encrypted, old_password, new_password).unwrap();
assert!(!engine.verify_password(&reencrypted, old_password));
assert!(engine.verify_password(&reencrypted, new_password));
let decrypted = engine.decrypt_to_string(&reencrypted, new_password).unwrap();
assert_eq!(plaintext, decrypted);
}
#[test]
fn test_direct_encryption() {
let engine = CryptoEngine::new();
let plaintext = b"Direct encryption test";
let key = CryptoEngine::generate_random_key().unwrap();
let nonce = CryptoEngine::generate_nonce().unwrap();
let ciphertext = engine.encrypt_direct(plaintext, &key, &nonce).unwrap();
let decrypted = engine.decrypt_direct(&ciphertext, &key, &nonce).unwrap();
assert_eq!(plaintext, decrypted.as_slice());
}
#[test]
fn test_encryption_options() {
let engine = CryptoEngine::new();
let plaintext = "Test with options";
let password = "test_password";
let options = EncryptionOptions::new()
.with_description("Test secret")
.with_type(SecretType::ApiKey)
.with_performance_profile(PerformanceProfile::Fast);
let encrypted = engine.encrypt_string(plaintext, password, options).unwrap();
let metadata = encrypted.metadata();
assert_eq!(metadata.description.as_ref().unwrap(), "Test secret");
assert_eq!(metadata.secret_type.as_ref().unwrap(), &SecretType::ApiKey);
let decrypted = engine.decrypt_to_string(&encrypted, password).unwrap();
assert_eq!(plaintext, decrypted);
}
#[test]
fn test_performance_benchmark() {
let engine = CryptoEngine::new();
let benchmark = engine.benchmark_performance().unwrap();
println!("Performance Benchmark:");
println!(" Profile: {:?}", benchmark.profile);
println!(" Key derivation: {:.2}ms", benchmark.key_derivation_ms);
println!(" Encryption: {:.3}ms", benchmark.encryption_ms);
println!(" Decryption: {:.3}ms", benchmark.decryption_ms);
println!(" Total: {:.2}ms", benchmark.total_ms);
println!(" Encryption throughput: {:.2} MB/s", benchmark.encryption_throughput_mbps());
println!(" Decryption throughput: {:.2} MB/s", benchmark.decryption_throughput_mbps());
println!(" Meets target (<1ms): {}", benchmark.meets_performance_target());
assert!(benchmark.key_derivation_ms > 0.0);
assert!(benchmark.encryption_ms > 0.0);
assert!(benchmark.decryption_ms > 0.0);
assert!(benchmark.total_ms > 0.0);
}
#[test]
fn test_wrong_password_fails() {
let engine = CryptoEngine::new();
let plaintext = "Secret data";
let password = "correct_password";
let wrong_password = "wrong_password";
let encrypted = engine.encrypt_string(plaintext, password, EncryptionOptions::new()).unwrap();
assert!(!engine.verify_password(&encrypted, wrong_password));
let result = engine.decrypt_to_string(&encrypted, wrong_password);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), CryptoError::AuthenticationFailed));
}
}