Skip to main content

shadow_crypt_core/v3/
crypt.rs

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