zlayer_secrets/
encryption.rs1use chacha20poly1305::{
7 aead::{Aead, KeyInit},
8 XChaCha20Poly1305, XNonce,
9};
10use rand::rngs::OsRng;
11use rand::TryRngCore;
12use zeroize::Zeroizing;
13
14use crate::{Result, SecretsError};
15
16pub const NONCE_SIZE: usize = 24;
18
19pub const KEY_SIZE: usize = 32;
21
22#[derive(Clone)]
27pub struct EncryptionKey {
28 key: Zeroizing<[u8; KEY_SIZE]>,
29}
30
31impl EncryptionKey {
32 pub fn derive_from_password(password: &str, salt: &[u8]) -> Result<Self> {
41 use argon2::{Algorithm, Argon2, Params, Version};
42
43 let params = Params::new(
46 19 * 1024, 2, 1, Some(KEY_SIZE),
50 )
51 .map_err(|e| SecretsError::Encryption(format!("Invalid Argon2 params: {e}")))?;
52
53 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
54
55 let mut key_bytes = Zeroizing::new([0u8; KEY_SIZE]);
56 argon2
57 .hash_password_into(password.as_bytes(), salt, key_bytes.as_mut())
58 .map_err(|e| SecretsError::Encryption(format!("Key derivation failed: {e}")))?;
59
60 Ok(Self { key: key_bytes })
61 }
62
63 #[must_use]
70 pub fn generate() -> Self {
71 let mut key_bytes = Zeroizing::new([0u8; KEY_SIZE]);
72 OsRng
73 .try_fill_bytes(key_bytes.as_mut())
74 .expect("OS RNG failed");
75 Self { key: key_bytes }
76 }
77
78 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
86 if bytes.len() != KEY_SIZE {
87 return Err(SecretsError::Encryption(format!(
88 "Invalid key length: expected {KEY_SIZE} bytes, got {}",
89 bytes.len()
90 )));
91 }
92
93 let mut key_bytes = Zeroizing::new([0u8; KEY_SIZE]);
94 key_bytes.copy_from_slice(bytes);
95 Ok(Self { key: key_bytes })
96 }
97
98 #[inline]
102 #[must_use]
103 pub fn as_bytes(&self) -> &[u8] {
104 self.key.as_ref()
105 }
106
107 pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
121 let cipher = XChaCha20Poly1305::new_from_slice(self.key.as_ref())
122 .map_err(|e| SecretsError::Encryption(format!("Failed to create cipher: {e}")))?;
123
124 let mut nonce_bytes = [0u8; NONCE_SIZE];
126 OsRng
127 .try_fill_bytes(&mut nonce_bytes)
128 .expect("OS RNG failed");
129 let nonce = XNonce::from_slice(&nonce_bytes);
130
131 let ciphertext = cipher
133 .encrypt(nonce, plaintext)
134 .map_err(|e| SecretsError::Encryption(format!("Encryption failed: {e}")))?;
135
136 let mut result = Vec::with_capacity(NONCE_SIZE + ciphertext.len());
138 result.extend_from_slice(&nonce_bytes);
139 result.extend_from_slice(&ciphertext);
140
141 Ok(result)
142 }
143
144 pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
156 if data.len() < NONCE_SIZE {
157 return Err(SecretsError::Decryption(format!(
158 "Data too short: expected at least {NONCE_SIZE} bytes for nonce, got {}",
159 data.len()
160 )));
161 }
162
163 let cipher = XChaCha20Poly1305::new_from_slice(self.key.as_ref())
164 .map_err(|e| SecretsError::Decryption(format!("Failed to create cipher: {e}")))?;
165
166 let (nonce_bytes, ciphertext) = data.split_at(NONCE_SIZE);
168 let nonce = XNonce::from_slice(nonce_bytes);
169
170 cipher
172 .decrypt(nonce, ciphertext)
173 .map_err(|e| SecretsError::Decryption(format!("Decryption failed: {e}")))
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_generate_key() {
183 let key = EncryptionKey::generate();
184 assert_eq!(key.as_bytes().len(), KEY_SIZE);
185
186 let key2 = EncryptionKey::generate();
188 assert_ne!(key.as_bytes(), key2.as_bytes());
189 }
190
191 #[test]
192 fn test_from_bytes_valid() {
193 let bytes = [42u8; KEY_SIZE];
194 let key = EncryptionKey::from_bytes(&bytes).unwrap();
195 assert_eq!(key.as_bytes(), &bytes);
196 }
197
198 #[test]
199 fn test_from_bytes_invalid_length() {
200 let bytes = [0u8; 16]; let result = EncryptionKey::from_bytes(&bytes);
202 assert!(result.is_err());
203
204 let bytes = [0u8; 64]; let result = EncryptionKey::from_bytes(&bytes);
206 assert!(result.is_err());
207 }
208
209 #[test]
210 fn test_derive_from_password() {
211 let salt = b"unique_salt_1234";
212 let key = EncryptionKey::derive_from_password("my_secure_password", salt).unwrap();
213 assert_eq!(key.as_bytes().len(), KEY_SIZE);
214
215 let key2 = EncryptionKey::derive_from_password("my_secure_password", salt).unwrap();
217 assert_eq!(key.as_bytes(), key2.as_bytes());
218
219 let key3 = EncryptionKey::derive_from_password("different_password", salt).unwrap();
221 assert_ne!(key.as_bytes(), key3.as_bytes());
222
223 let key4 =
225 EncryptionKey::derive_from_password("my_secure_password", b"different_salt__").unwrap();
226 assert_ne!(key.as_bytes(), key4.as_bytes());
227 }
228
229 #[test]
230 fn test_encrypt_decrypt_roundtrip() {
231 let key = EncryptionKey::generate();
232 let plaintext = b"Hello, World! This is a secret message.";
233
234 let encrypted = key.encrypt(plaintext).unwrap();
235
236 assert!(encrypted.len() > plaintext.len());
238
239 let decrypted = key.decrypt(&encrypted).unwrap();
240 assert_eq!(decrypted, plaintext);
241 }
242
243 #[test]
244 fn test_encrypt_produces_different_ciphertext() {
245 let key = EncryptionKey::generate();
246 let plaintext = b"Same message";
247
248 let encrypted1 = key.encrypt(plaintext).unwrap();
249 let encrypted2 = key.encrypt(plaintext).unwrap();
250
251 assert_ne!(encrypted1, encrypted2);
253
254 assert_eq!(key.decrypt(&encrypted1).unwrap(), plaintext);
256 assert_eq!(key.decrypt(&encrypted2).unwrap(), plaintext);
257 }
258
259 #[test]
260 fn test_decrypt_with_wrong_key_fails() {
261 let key1 = EncryptionKey::generate();
262 let key2 = EncryptionKey::generate();
263 let plaintext = b"Secret data";
264
265 let encrypted = key1.encrypt(plaintext).unwrap();
266 let result = key2.decrypt(&encrypted);
267
268 assert!(result.is_err());
269 }
270
271 #[test]
272 fn test_decrypt_tampered_data_fails() {
273 let key = EncryptionKey::generate();
274 let plaintext = b"Important data";
275
276 let mut encrypted = key.encrypt(plaintext).unwrap();
277
278 if let Some(byte) = encrypted.get_mut(NONCE_SIZE + 5) {
280 *byte ^= 0xFF;
281 }
282
283 let result = key.decrypt(&encrypted);
284 assert!(result.is_err());
285 }
286
287 #[test]
288 fn test_decrypt_too_short_data() {
289 let key = EncryptionKey::generate();
290 let short_data = [0u8; 10]; let result = key.decrypt(&short_data);
293 assert!(result.is_err());
294 }
295
296 #[test]
297 fn test_encrypt_empty_plaintext() {
298 let key = EncryptionKey::generate();
299 let plaintext = b"";
300
301 let encrypted = key.encrypt(plaintext).unwrap();
302 let decrypted = key.decrypt(&encrypted).unwrap();
303
304 assert_eq!(decrypted, plaintext);
305 }
306
307 #[test]
308 fn test_password_derived_key_encrypt_decrypt() {
309 let salt = b"random_salt_here";
310 let key = EncryptionKey::derive_from_password("password123", salt).unwrap();
311 let plaintext = b"Sensitive information";
312
313 let encrypted = key.encrypt(plaintext).unwrap();
314 let decrypted = key.decrypt(&encrypted).unwrap();
315
316 assert_eq!(decrypted, plaintext);
317 }
318}