Skip to main content

shadow_crypt_core/v1/
crypt.rs

1use chacha20poly1305::{KeyInit, XChaCha20Poly1305, aead::Aead};
2
3use crate::{algorithm::Algorithm, errors::CryptError, memory::SecureBytes};
4
5pub fn encrypt_bytes(
6    plaintext: &[u8],
7    key: &[u8; 32],
8    nonce: &[u8; 24],
9) -> Result<(Vec<u8>, Algorithm), CryptError> {
10    let cipher = XChaCha20Poly1305::new(key.into());
11    let ciphertext = cipher
12        .encrypt(nonce.into(), plaintext)
13        .map_err(|e| CryptError::EncryptionError(format!("Encryption failed: {}", e)))?;
14
15    Ok((ciphertext, Algorithm::XChaCha20Poly1305))
16}
17
18/// Decrypts the given ciphertext, returning the plaintext as zeroizing [`SecureBytes`].
19pub fn decrypt_bytes(
20    ciphertext: &[u8],
21    key: &[u8; 32],
22    nonce: &[u8; 24],
23) -> Result<(SecureBytes, Algorithm), CryptError> {
24    let cipher = XChaCha20Poly1305::new(key.into());
25    let plaintext = cipher
26        .decrypt(nonce.into(), ciphertext)
27        // The AEAD reports authentication failure without a cause; wrong
28        // password and corruption are indistinguishable by design.
29        .map_err(|_| {
30            CryptError::DecryptionError(
31                "authentication failed (wrong password, or the file is corrupted)".to_string(),
32            )
33        })?;
34
35    Ok((SecureBytes::new(plaintext), Algorithm::XChaCha20Poly1305))
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn test_encrypt_decrypt_round_trip() {
44        let plaintext = b"Hello, world!";
45        let key = [0u8; 32];
46        let nonce = [0u8; 24];
47
48        let (ciphertext, algorithm) = encrypt_bytes(plaintext, &key, &nonce).unwrap();
49        assert_eq!(algorithm, Algorithm::XChaCha20Poly1305);
50        assert_ne!(ciphertext, plaintext);
51
52        let (decrypted, algorithm) = decrypt_bytes(&ciphertext, &key, &nonce).unwrap();
53        assert_eq!(algorithm, Algorithm::XChaCha20Poly1305);
54        assert_eq!(decrypted.as_slice(), plaintext);
55    }
56
57    #[test]
58    fn test_encrypt_different_nonce_produces_different_ciphertext() {
59        let plaintext = b"Test message";
60        let key = [1u8; 32];
61        let nonce1 = [0u8; 24];
62        let nonce2 = [1u8; 24];
63
64        let (ciphertext1, _) = encrypt_bytes(plaintext, &key, &nonce1).unwrap();
65        let (ciphertext2, _) = encrypt_bytes(plaintext, &key, &nonce2).unwrap();
66
67        assert_ne!(ciphertext1, ciphertext2);
68    }
69
70    #[test]
71    fn test_encrypt_same_inputs_produce_same_output() {
72        let plaintext = b"Deterministic test";
73        let key = [2u8; 32];
74        let nonce = [2u8; 24];
75
76        let (ciphertext1, _) = encrypt_bytes(plaintext, &key, &nonce).unwrap();
77        let (ciphertext2, _) = encrypt_bytes(plaintext, &key, &nonce).unwrap();
78
79        assert_eq!(ciphertext1, ciphertext2);
80    }
81
82    #[test]
83    fn test_decrypt_with_wrong_key_fails() {
84        let plaintext = b"Secret message";
85        let key = [3u8; 32];
86        let wrong_key = [4u8; 32];
87        let nonce = [3u8; 24];
88
89        let (ciphertext, _) = encrypt_bytes(plaintext, &key, &nonce).unwrap();
90        let result = decrypt_bytes(&ciphertext, &wrong_key, &nonce);
91
92        assert!(result.is_err());
93    }
94
95    #[test]
96    fn test_decrypt_with_wrong_nonce_fails() {
97        let plaintext = b"Secret message";
98        let key = [5u8; 32];
99        let nonce = [5u8; 24];
100        let wrong_nonce = [6u8; 24];
101
102        let (ciphertext, _) = encrypt_bytes(plaintext, &key, &nonce).unwrap();
103        let result = decrypt_bytes(&ciphertext, &key, &wrong_nonce);
104
105        assert!(result.is_err());
106    }
107
108    #[test]
109    fn test_encrypt_empty_plaintext() {
110        let plaintext = b"";
111        let key = [7u8; 32];
112        let nonce = [7u8; 24];
113
114        let (ciphertext, algorithm) = encrypt_bytes(plaintext, &key, &nonce).unwrap();
115        assert_eq!(algorithm, Algorithm::XChaCha20Poly1305);
116
117        let (decrypted, algorithm) = decrypt_bytes(&ciphertext, &key, &nonce).unwrap();
118        assert_eq!(algorithm, Algorithm::XChaCha20Poly1305);
119        assert_eq!(decrypted.as_slice(), plaintext);
120    }
121}