rust_secure_logger/
encryption.rs1use aes_gcm::{
6 aead::{Aead, KeyInit, OsRng},
7 Aes256Gcm, Nonce,
8};
9use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
10use rand::RngCore;
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14#[derive(Error, Debug)]
16pub enum EncryptionError {
17 #[error("Encryption failed: {0}")]
18 EncryptionFailed(String),
19
20 #[error("Decryption failed: {0}")]
21 DecryptionFailed(String),
22
23 #[error("Invalid key length")]
24 InvalidKeyLength,
25
26 #[error("Base64 decode error: {0}")]
27 Base64Error(String),
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct EncryptedLogEntry {
33 pub ciphertext: String, pub nonce: String, pub timestamp: chrono::DateTime<chrono::Utc>,
36 pub entry_id: String,
37}
38
39pub struct EncryptionKey {
41 key: Vec<u8>,
42}
43
44impl EncryptionKey {
45 pub fn generate() -> Self {
47 let mut key = vec![0u8; 32];
48 OsRng.fill_bytes(&mut key);
49 Self { key }
50 }
51
52 pub fn from_bytes(bytes: &[u8]) -> Result<Self, EncryptionError> {
54 if bytes.len() != 32 {
55 return Err(EncryptionError::InvalidKeyLength);
56 }
57 Ok(Self {
58 key: bytes.to_vec(),
59 })
60 }
61
62 pub fn as_bytes(&self) -> &[u8] {
64 &self.key
65 }
66}
67
68impl Drop for EncryptionKey {
69 fn drop(&mut self) {
70 for byte in &mut self.key {
72 *byte = 0;
73 }
74 }
75}
76
77pub struct LogEncryptor {
79 cipher: Aes256Gcm,
80}
81
82impl LogEncryptor {
83 pub fn new(key: &EncryptionKey) -> Result<Self, EncryptionError> {
85 let cipher = Aes256Gcm::new_from_slice(key.as_bytes())
86 .map_err(|e| EncryptionError::EncryptionFailed(e.to_string()))?;
87 Ok(Self { cipher })
88 }
89
90 pub fn encrypt(&self, plaintext: &str, entry_id: &str) -> Result<EncryptedLogEntry, EncryptionError> {
92 let mut nonce_bytes = [0u8; 12];
93 OsRng.fill_bytes(&mut nonce_bytes);
94 let nonce = Nonce::from_slice(&nonce_bytes);
95
96 let ciphertext = self
97 .cipher
98 .encrypt(nonce, plaintext.as_bytes())
99 .map_err(|e| EncryptionError::EncryptionFailed(e.to_string()))?;
100
101 Ok(EncryptedLogEntry {
102 ciphertext: BASE64.encode(&ciphertext),
103 nonce: BASE64.encode(nonce_bytes),
104 timestamp: chrono::Utc::now(),
105 entry_id: entry_id.to_string(),
106 })
107 }
108
109 pub fn decrypt(&self, encrypted: &EncryptedLogEntry) -> Result<String, EncryptionError> {
111 let ciphertext = BASE64
112 .decode(&encrypted.ciphertext)
113 .map_err(|e| EncryptionError::Base64Error(e.to_string()))?;
114
115 let nonce_bytes = BASE64
116 .decode(&encrypted.nonce)
117 .map_err(|e| EncryptionError::Base64Error(e.to_string()))?;
118
119 if nonce_bytes.len() != 12 {
120 return Err(EncryptionError::DecryptionFailed("Invalid nonce length".to_string()));
121 }
122
123 let nonce = Nonce::from_slice(&nonce_bytes);
124
125 let plaintext = self
126 .cipher
127 .decrypt(nonce, ciphertext.as_ref())
128 .map_err(|e| EncryptionError::DecryptionFailed(e.to_string()))?;
129
130 String::from_utf8(plaintext)
131 .map_err(|e| EncryptionError::DecryptionFailed(e.to_string()))
132 }
133
134 pub fn encrypt_batch(&self, entries: &[(&str, &str)]) -> Vec<Result<EncryptedLogEntry, EncryptionError>> {
136 entries
137 .iter()
138 .map(|(plaintext, entry_id)| self.encrypt(plaintext, entry_id))
139 .collect()
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_encryption_decryption() {
149 let key = EncryptionKey::generate();
150 let encryptor = LogEncryptor::new(&key).unwrap();
151
152 let plaintext = "Sensitive log entry: User 12345 accessed financial records";
153 let entry_id = "LOG-001";
154
155 let encrypted = encryptor.encrypt(plaintext, entry_id).unwrap();
156 let decrypted = encryptor.decrypt(&encrypted).unwrap();
157
158 assert_eq!(plaintext, decrypted);
159 assert_eq!(encrypted.entry_id, entry_id);
160 }
161
162 #[test]
163 fn test_different_nonces() {
164 let key = EncryptionKey::generate();
165 let encryptor = LogEncryptor::new(&key).unwrap();
166
167 let plaintext = "Same message";
168 let enc1 = encryptor.encrypt(plaintext, "LOG-001").unwrap();
169 let enc2 = encryptor.encrypt(plaintext, "LOG-002").unwrap();
170
171 assert_ne!(enc1.ciphertext, enc2.ciphertext);
173 assert_ne!(enc1.nonce, enc2.nonce);
174 }
175
176 #[test]
177 fn test_invalid_key_length() {
178 let short_key = vec![0u8; 16];
179 let result = EncryptionKey::from_bytes(&short_key);
180 assert!(result.is_err());
181 }
182
183 #[test]
184 fn test_batch_encryption() {
185 let key = EncryptionKey::generate();
186 let encryptor = LogEncryptor::new(&key).unwrap();
187
188 let entries = vec![
189 ("Log entry 1", "LOG-001"),
190 ("Log entry 2", "LOG-002"),
191 ("Log entry 3", "LOG-003"),
192 ];
193
194 let results = encryptor.encrypt_batch(&entries);
195 assert_eq!(results.len(), 3);
196 assert!(results.iter().all(|r| r.is_ok()));
197 }
198
199 #[test]
200 fn test_encrypted_entry_serialization() {
201 let key = EncryptionKey::generate();
202 let encryptor = LogEncryptor::new(&key).unwrap();
203
204 let encrypted = encryptor.encrypt("Test message", "LOG-001").unwrap();
205 let json = serde_json::to_string(&encrypted).unwrap();
206 let deserialized: EncryptedLogEntry = serde_json::from_str(&json).unwrap();
207
208 let decrypted = encryptor.decrypt(&deserialized).unwrap();
209 assert_eq!(decrypted, "Test message");
210 }
211}