Skip to main content

shadow_crypt_core/v2/
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 v2 the associated data is always a serialized
11/// [`HeaderBinding`](crate::v2::header::HeaderBinding), which ties the
12/// ciphertext to its header and its purpose (filename 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        .map_err(|e| CryptError::DecryptionError(format!("Decryption failed: {}", e)))?;
52
53    Ok((SecureBytes::new(plaintext), Algorithm::XChaCha20Poly1305))
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn test_encrypt_decrypt_round_trip() {
62        let plaintext = b"Hello, world!";
63        let key = [0u8; 32];
64        let nonce = [0u8; 24];
65        let aad = b"header binding";
66
67        let (ciphertext, algorithm) = encrypt_bytes(plaintext, &key, &nonce, aad).unwrap();
68        assert_eq!(algorithm, Algorithm::XChaCha20Poly1305);
69        assert_ne!(ciphertext, plaintext);
70
71        let (decrypted, _) = decrypt_bytes(&ciphertext, &key, &nonce, aad).unwrap();
72        assert_eq!(decrypted.as_slice(), plaintext);
73    }
74
75    #[test]
76    fn test_decrypt_with_wrong_aad_fails() {
77        let plaintext = b"Secret message";
78        let key = [1u8; 32];
79        let nonce = [1u8; 24];
80
81        let (ciphertext, _) = encrypt_bytes(plaintext, &key, &nonce, b"aad one").unwrap();
82        let result = decrypt_bytes(&ciphertext, &key, &nonce, b"aad two");
83
84        assert!(result.is_err());
85    }
86
87    #[test]
88    fn test_decrypt_with_wrong_key_fails() {
89        let plaintext = b"Secret message";
90        let key = [3u8; 32];
91        let wrong_key = [4u8; 32];
92        let nonce = [3u8; 24];
93        let aad = b"aad";
94
95        let (ciphertext, _) = encrypt_bytes(plaintext, &key, &nonce, aad).unwrap();
96        assert!(decrypt_bytes(&ciphertext, &wrong_key, &nonce, aad).is_err());
97    }
98
99    #[test]
100    fn test_decrypt_with_wrong_nonce_fails() {
101        let plaintext = b"Secret message";
102        let key = [5u8; 32];
103        let nonce = [5u8; 24];
104        let wrong_nonce = [6u8; 24];
105        let aad = b"aad";
106
107        let (ciphertext, _) = encrypt_bytes(plaintext, &key, &nonce, aad).unwrap();
108        assert!(decrypt_bytes(&ciphertext, &key, &wrong_nonce, aad).is_err());
109    }
110
111    #[test]
112    fn test_encrypt_empty_plaintext_and_empty_aad() {
113        let plaintext = b"";
114        let key = [7u8; 32];
115        let nonce = [7u8; 24];
116        let aad = b"";
117
118        let (ciphertext, _) = encrypt_bytes(plaintext, &key, &nonce, aad).unwrap();
119        let (decrypted, _) = decrypt_bytes(&ciphertext, &key, &nonce, aad).unwrap();
120        assert_eq!(decrypted.as_slice(), plaintext);
121    }
122}