1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum CipherMode {
13 Xor,
15 XorWithNonce,
17}
18
19#[derive(Debug, Clone)]
21pub struct EncryptionLayerConfig {
22 pub mode: CipherMode,
24 pub key: Vec<u8>,
26 pub nonce_size: usize,
28}
29
30impl Default for EncryptionLayerConfig {
31 fn default() -> Self {
32 Self {
33 mode: CipherMode::Xor,
34 key: vec![0u8; 32],
35 nonce_size: 12,
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct EncryptedBlock {
43 pub cid: String,
45 pub ciphertext: Vec<u8>,
47 pub nonce: Option<Vec<u8>>,
49 pub original_size: usize,
51}
52
53#[derive(Debug, Clone)]
55pub struct EncryptionLayerStats {
56 pub blocks_encrypted: u64,
58 pub blocks_decrypted: u64,
60 pub bytes_encrypted: u64,
62 pub bytes_decrypted: u64,
64}
65
66pub struct StorageEncryptionLayer {
71 config: EncryptionLayerConfig,
72 blocks_encrypted: u64,
73 blocks_decrypted: u64,
74 bytes_encrypted: u64,
75 bytes_decrypted: u64,
76}
77
78impl StorageEncryptionLayer {
79 pub fn new(config: EncryptionLayerConfig) -> Self {
81 Self {
82 config,
83 blocks_encrypted: 0,
84 blocks_decrypted: 0,
85 bytes_encrypted: 0,
86 bytes_decrypted: 0,
87 }
88 }
89
90 pub fn encrypt(&mut self, cid: &str, plaintext: &[u8]) -> EncryptedBlock {
97 let (ciphertext, nonce) = match self.config.mode {
98 CipherMode::Xor => {
99 let ct = xor_with_repeating_key(plaintext, &self.config.key);
100 (ct, None)
101 }
102 CipherMode::XorWithNonce => {
103 let nonce = Self::generate_nonce(cid, self.config.nonce_size);
104 let working_key = Self::derive_key(&self.config.key, &nonce);
105 let ct = xor_with_repeating_key(plaintext, &working_key);
106 (ct, Some(nonce))
107 }
108 };
109
110 self.blocks_encrypted += 1;
111 self.bytes_encrypted += plaintext.len() as u64;
112
113 EncryptedBlock {
114 cid: cid.to_string(),
115 ciphertext,
116 nonce,
117 original_size: plaintext.len(),
118 }
119 }
120
121 pub fn decrypt(&mut self, block: &EncryptedBlock) -> Result<Vec<u8>, String> {
125 if block.ciphertext.len() != block.original_size {
126 return Err(format!(
127 "ciphertext length {} does not match original_size {}",
128 block.ciphertext.len(),
129 block.original_size
130 ));
131 }
132
133 let plaintext = match self.config.mode {
134 CipherMode::Xor => xor_with_repeating_key(&block.ciphertext, &self.config.key),
135 CipherMode::XorWithNonce => {
136 let nonce = block.nonce.as_ref().ok_or_else(|| {
137 "XorWithNonce mode requires a nonce in the encrypted block".to_string()
138 })?;
139 let working_key = Self::derive_key(&self.config.key, nonce);
140 xor_with_repeating_key(&block.ciphertext, &working_key)
141 }
142 };
143
144 self.blocks_decrypted += 1;
145 self.bytes_decrypted += plaintext.len() as u64;
146
147 Ok(plaintext)
148 }
149
150 pub fn derive_key(base_key: &[u8], nonce: &[u8]) -> Vec<u8> {
155 if nonce.is_empty() {
156 return base_key.to_vec();
157 }
158 base_key
159 .iter()
160 .enumerate()
161 .map(|(i, &b)| b ^ nonce[i % nonce.len()])
162 .collect()
163 }
164
165 pub fn generate_nonce(cid: &str, size: usize) -> Vec<u8> {
170 let mut nonce = Vec::with_capacity(size);
171 let cid_bytes = cid.as_bytes();
172
173 let mut remaining = size;
175 let mut round: u64 = 0;
176 while remaining > 0 {
177 let hash = fnv1a_with_seed(cid_bytes, round);
178 let hash_bytes = hash.to_le_bytes();
179 let take = remaining.min(hash_bytes.len());
180 nonce.extend_from_slice(&hash_bytes[..take]);
181 remaining = remaining.saturating_sub(take);
182 round += 1;
183 }
184
185 nonce.truncate(size);
186 nonce
187 }
188
189 pub fn is_encrypted(data: &[u8]) -> bool {
194 if data.is_empty() {
195 return false;
196 }
197
198 let mut freq = [0u64; 256];
199 for &b in data {
200 freq[b as usize] += 1;
201 }
202
203 let len = data.len() as f64;
204 let mut entropy = 0.0_f64;
205 for &count in &freq {
206 if count > 0 {
207 let p = count as f64 / len;
208 entropy -= p * p.log2();
209 }
210 }
211
212 entropy > 7.5
213 }
214
215 pub fn stats(&self) -> EncryptionLayerStats {
217 EncryptionLayerStats {
218 blocks_encrypted: self.blocks_encrypted,
219 blocks_decrypted: self.blocks_decrypted,
220 bytes_encrypted: self.bytes_encrypted,
221 bytes_decrypted: self.bytes_decrypted,
222 }
223 }
224}
225
226fn fnv1a_with_seed(data: &[u8], seed: u64) -> u64 {
228 let mut hash: u64 = 0xcbf29ce484222325_u64.wrapping_add(seed.wrapping_mul(0x100000001b3));
229 for &byte in data {
230 hash ^= byte as u64;
231 hash = hash.wrapping_mul(0x100000001b3);
232 }
233 hash
234}
235
236fn xor_with_repeating_key(data: &[u8], key: &[u8]) -> Vec<u8> {
238 if key.is_empty() {
239 return data.to_vec();
240 }
241 data.iter()
242 .enumerate()
243 .map(|(i, &b)| b ^ key[i % key.len()])
244 .collect()
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 fn make_config(mode: CipherMode) -> EncryptionLayerConfig {
252 EncryptionLayerConfig {
253 mode,
254 key: vec![0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x9A],
255 nonce_size: 12,
256 }
257 }
258
259 #[test]
260 fn test_encrypt_decrypt_roundtrip_xor() {
261 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::Xor));
262 let plaintext = b"Hello, IPFRS storage encryption!";
263 let encrypted = layer.encrypt("QmTest1", plaintext);
264 let decrypted = layer.decrypt(&encrypted).expect("decrypt should succeed");
265 assert_eq!(decrypted, plaintext);
266 }
267
268 #[test]
269 fn test_encrypt_decrypt_roundtrip_xor_with_nonce() {
270 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::XorWithNonce));
271 let plaintext = b"Hello, IPFRS storage encryption with nonce!";
272 let encrypted = layer.encrypt("QmTest2", plaintext);
273 assert!(encrypted.nonce.is_some());
274 let decrypted = layer.decrypt(&encrypted).expect("decrypt should succeed");
275 assert_eq!(decrypted, plaintext);
276 }
277
278 #[test]
279 fn test_xor_mode_correctness() {
280 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::Xor));
281 let plaintext = vec![0x00, 0xFF, 0x55, 0xAA];
282 let encrypted = layer.encrypt("QmXor", &plaintext);
283 assert_eq!(encrypted.ciphertext, vec![0xAB, 0x32, 0xBA, 0xB8]);
285 }
286
287 #[test]
288 fn test_ciphertext_differs_from_plaintext() {
289 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::Xor));
290 let plaintext = b"This should be encrypted";
291 let encrypted = layer.encrypt("QmDiff", plaintext);
292 assert_ne!(encrypted.ciphertext, plaintext);
293 }
294
295 #[test]
296 fn test_different_keys_different_ciphertext() {
297 let config1 = EncryptionLayerConfig {
298 mode: CipherMode::Xor,
299 key: vec![0x01, 0x02, 0x03, 0x04],
300 nonce_size: 12,
301 };
302 let config2 = EncryptionLayerConfig {
303 mode: CipherMode::Xor,
304 key: vec![0x05, 0x06, 0x07, 0x08],
305 nonce_size: 12,
306 };
307 let mut layer1 = StorageEncryptionLayer::new(config1);
308 let mut layer2 = StorageEncryptionLayer::new(config2);
309 let plaintext = b"Same plaintext, different keys";
310 let enc1 = layer1.encrypt("QmKeys", plaintext);
311 let enc2 = layer2.encrypt("QmKeys", plaintext);
312 assert_ne!(enc1.ciphertext, enc2.ciphertext);
313 }
314
315 #[test]
316 fn test_deterministic_nonce_from_cid() {
317 let nonce1 = StorageEncryptionLayer::generate_nonce("QmDeterministic", 12);
318 let nonce2 = StorageEncryptionLayer::generate_nonce("QmDeterministic", 12);
319 assert_eq!(nonce1, nonce2);
320 assert_eq!(nonce1.len(), 12);
321 }
322
323 #[test]
324 fn test_different_cids_different_nonces() {
325 let nonce1 = StorageEncryptionLayer::generate_nonce("QmCid1", 12);
326 let nonce2 = StorageEncryptionLayer::generate_nonce("QmCid2", 12);
327 assert_ne!(nonce1, nonce2);
328 }
329
330 #[test]
331 fn test_derive_key() {
332 let base_key = vec![0xAA, 0xBB, 0xCC, 0xDD];
333 let nonce = vec![0x11, 0x22];
334 let derived = StorageEncryptionLayer::derive_key(&base_key, &nonce);
335 assert_eq!(derived, vec![0xBB, 0x99, 0xDD, 0xFF]);
337 }
338
339 #[test]
340 fn test_derive_key_empty_nonce() {
341 let base_key = vec![0xAA, 0xBB, 0xCC];
342 let derived = StorageEncryptionLayer::derive_key(&base_key, &[]);
343 assert_eq!(derived, base_key);
344 }
345
346 #[test]
347 fn test_empty_plaintext() {
348 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::Xor));
349 let encrypted = layer.encrypt("QmEmpty", b"");
350 assert!(encrypted.ciphertext.is_empty());
351 assert_eq!(encrypted.original_size, 0);
352 let decrypted = layer.decrypt(&encrypted).expect("decrypt should succeed");
353 assert!(decrypted.is_empty());
354 }
355
356 #[test]
357 fn test_empty_plaintext_with_nonce() {
358 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::XorWithNonce));
359 let encrypted = layer.encrypt("QmEmptyNonce", b"");
360 assert!(encrypted.ciphertext.is_empty());
361 let decrypted = layer.decrypt(&encrypted).expect("decrypt should succeed");
362 assert!(decrypted.is_empty());
363 }
364
365 #[test]
366 fn test_stats_tracking() {
367 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::Xor));
368 let s = layer.stats();
369 assert_eq!(s.blocks_encrypted, 0);
370 assert_eq!(s.blocks_decrypted, 0);
371
372 let data1 = b"first block";
373 let enc1 = layer.encrypt("QmStats1", data1);
374 let data2 = b"second block data";
375 let enc2 = layer.encrypt("QmStats2", data2);
376
377 let s = layer.stats();
378 assert_eq!(s.blocks_encrypted, 2);
379 assert_eq!(s.bytes_encrypted, (data1.len() + data2.len()) as u64);
380
381 let _ = layer.decrypt(&enc1).expect("decrypt should succeed");
382 let _ = layer.decrypt(&enc2).expect("decrypt should succeed");
383
384 let s = layer.stats();
385 assert_eq!(s.blocks_decrypted, 2);
386 assert_eq!(s.bytes_decrypted, (data1.len() + data2.len()) as u64);
387 }
388
389 #[test]
390 fn test_large_block() {
391 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::XorWithNonce));
392 let plaintext: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
393 let encrypted = layer.encrypt("QmLargeBlock", &plaintext);
394 assert_eq!(encrypted.ciphertext.len(), plaintext.len());
395 let decrypted = layer.decrypt(&encrypted).expect("decrypt should succeed");
396 assert_eq!(decrypted, plaintext);
397 }
398
399 #[test]
400 fn test_key_shorter_than_data() {
401 let config = EncryptionLayerConfig {
402 mode: CipherMode::Xor,
403 key: vec![0xFF],
404 nonce_size: 12,
405 };
406 let mut layer = StorageEncryptionLayer::new(config);
407 let plaintext = vec![0x00, 0x01, 0x02, 0x03, 0x04];
408 let encrypted = layer.encrypt("QmShortKey", &plaintext);
409 assert_eq!(encrypted.ciphertext, vec![0xFF, 0xFE, 0xFD, 0xFC, 0xFB]);
411 let decrypted = layer.decrypt(&encrypted).expect("decrypt should succeed");
412 assert_eq!(decrypted, plaintext);
413 }
414
415 #[test]
416 fn test_same_cid_same_nonce() {
417 let nonce_a = StorageEncryptionLayer::generate_nonce("QmSameCid", 16);
418 let nonce_b = StorageEncryptionLayer::generate_nonce("QmSameCid", 16);
419 assert_eq!(nonce_a, nonce_b);
420 }
421
422 #[test]
423 fn test_is_encrypted_random_data() {
424 let data: Vec<u8> = (0..1000)
426 .map(|i: u64| {
427 let h = fnv1a_with_seed(&i.to_le_bytes(), 42);
428 (h & 0xFF) as u8
429 })
430 .collect();
431 assert!(StorageEncryptionLayer::is_encrypted(&data));
432 }
433
434 #[test]
435 fn test_is_encrypted_low_entropy() {
436 let data = vec![0xAA; 1000];
438 assert!(!StorageEncryptionLayer::is_encrypted(&data));
439 }
440
441 #[test]
442 fn test_is_encrypted_empty() {
443 assert!(!StorageEncryptionLayer::is_encrypted(&[]));
444 }
445
446 #[test]
447 fn test_decrypt_size_mismatch() {
448 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::Xor));
449 let bad_block = EncryptedBlock {
450 cid: "QmBad".to_string(),
451 ciphertext: vec![0x01, 0x02, 0x03],
452 nonce: None,
453 original_size: 5, };
455 let result = layer.decrypt(&bad_block);
456 assert!(result.is_err());
457 }
458
459 #[test]
460 fn test_decrypt_xor_with_nonce_missing_nonce() {
461 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::XorWithNonce));
462 let bad_block = EncryptedBlock {
463 cid: "QmNoNonce".to_string(),
464 ciphertext: vec![0x01, 0x02],
465 nonce: None, original_size: 2,
467 };
468 let result = layer.decrypt(&bad_block);
469 assert!(result.is_err());
470 }
471
472 #[test]
473 fn test_xor_with_nonce_different_cids_different_ciphertext() {
474 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::XorWithNonce));
475 let plaintext = b"Identical data for different CIDs";
476 let enc1 = layer.encrypt("QmCidA", plaintext);
477 let enc2 = layer.encrypt("QmCidB", plaintext);
478 assert_ne!(enc1.ciphertext, enc2.ciphertext);
479 }
480
481 #[test]
482 fn test_encrypted_block_cid_preserved() {
483 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::Xor));
484 let cid = "QmPreservedCid12345";
485 let encrypted = layer.encrypt(cid, b"data");
486 assert_eq!(encrypted.cid, cid);
487 }
488
489 #[test]
490 fn test_generate_nonce_various_sizes() {
491 for size in [0, 1, 8, 12, 16, 32, 64] {
492 let nonce = StorageEncryptionLayer::generate_nonce("QmVarySize", size);
493 assert_eq!(nonce.len(), size);
494 }
495 }
496
497 #[test]
498 fn test_xor_self_inverse() {
499 let key = vec![0x42, 0x73, 0x99];
501 let data = b"self inverse test data";
502 let encrypted = xor_with_repeating_key(data, &key);
503 let decrypted = xor_with_repeating_key(&encrypted, &key);
504 assert_eq!(decrypted, data);
505 }
506
507 #[test]
508 fn test_xor_with_empty_key() {
509 let data = b"no encryption";
510 let result = xor_with_repeating_key(data, &[]);
511 assert_eq!(result, data);
512 }
513
514 #[test]
515 fn test_default_config() {
516 let config = EncryptionLayerConfig::default();
517 assert_eq!(config.mode, CipherMode::Xor);
518 assert_eq!(config.key.len(), 32);
519 assert_eq!(config.nonce_size, 12);
520 }
521
522 #[test]
523 fn test_stats_initial_zeros() {
524 let layer = StorageEncryptionLayer::new(make_config(CipherMode::Xor));
525 let s = layer.stats();
526 assert_eq!(s.blocks_encrypted, 0);
527 assert_eq!(s.blocks_decrypted, 0);
528 assert_eq!(s.bytes_encrypted, 0);
529 assert_eq!(s.bytes_decrypted, 0);
530 }
531
532 #[test]
533 fn test_single_byte_data() {
534 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::XorWithNonce));
535 let plaintext = &[0x42];
536 let encrypted = layer.encrypt("QmSingleByte", plaintext);
537 let decrypted = layer.decrypt(&encrypted).expect("decrypt should succeed");
538 assert_eq!(decrypted, plaintext);
539 }
540
541 #[test]
542 fn test_all_byte_values() {
543 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::Xor));
544 let plaintext: Vec<u8> = (0..=255).collect();
545 let encrypted = layer.encrypt("QmAllBytes", &plaintext);
546 let decrypted = layer.decrypt(&encrypted).expect("decrypt should succeed");
547 assert_eq!(decrypted, plaintext);
548 }
549
550 #[test]
551 fn test_temp_dir_usage() {
552 let tmp = std::env::temp_dir().join("ipfrs_encryption_layer_test");
554 let mut layer = StorageEncryptionLayer::new(make_config(CipherMode::Xor));
555 let plaintext = b"temp dir test";
556 let encrypted = layer.encrypt("QmTmpDir", plaintext);
557 std::fs::write(&tmp, &encrypted.ciphertext).expect("write to temp should succeed");
558 let read_back = std::fs::read(&tmp).expect("read from temp should succeed");
559 assert_eq!(read_back, encrypted.ciphertext);
560 let _ = std::fs::remove_file(&tmp);
561 }
562}