bc_crypto/
symmetric_encryption.rs

1use chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, KeyInit};
2
3use crate::Result;
4
5pub const SYMMETRIC_KEY_SIZE: usize = 32;
6pub const SYMMETRIC_NONCE_SIZE: usize = 12;
7pub const SYMMETRIC_AUTH_SIZE: usize = 16;
8
9/// Symmetrically encrypts the given plaintext using ChaCha20-Poly1305 and the
10/// given additional authenticated data (AAD).
11///
12/// Returns the ciphertext and the authentication tag.
13pub fn aead_chacha20_poly1305_encrypt_with_aad(
14    plaintext: impl AsRef<[u8]>,
15    key: &[u8; SYMMETRIC_KEY_SIZE],
16    nonce: &[u8; SYMMETRIC_NONCE_SIZE],
17    aad: impl AsRef<[u8]>,
18) -> (Vec<u8>, [u8; SYMMETRIC_AUTH_SIZE]) {
19    let cipher = ChaCha20Poly1305::new(key.into());
20    let mut buffer = plaintext.as_ref().to_vec();
21    let auth = cipher
22        .encrypt_in_place_detached(nonce.into(), aad.as_ref(), &mut buffer)
23        .unwrap();
24    (buffer, auth.to_vec().try_into().unwrap())
25}
26
27/// Symmetrically encrypts the given plaintext using ChaCha20-Poly1305.
28///
29/// Returns the ciphertext and the authentication tag.
30pub fn aead_chacha20_poly1305_encrypt(
31    plaintext: impl AsRef<[u8]>,
32    key: &[u8; SYMMETRIC_KEY_SIZE],
33    nonce: &[u8; SYMMETRIC_NONCE_SIZE],
34) -> (Vec<u8>, [u8; SYMMETRIC_AUTH_SIZE]) {
35    aead_chacha20_poly1305_encrypt_with_aad(plaintext, key, nonce, [])
36}
37
38/// Symmetrically decrypts the given ciphertext using ChaCha20-Poly1305 and the
39/// given additional authenticated data (AAD).
40///
41/// Returns the plaintext, or an error if the decryption failed.
42pub fn aead_chacha20_poly1305_decrypt_with_aad<D1, D2>(
43    ciphertext: D1,
44    key: &[u8; SYMMETRIC_KEY_SIZE],
45    nonce: &[u8; SYMMETRIC_NONCE_SIZE],
46    aad: D2,
47    auth: &[u8; SYMMETRIC_AUTH_SIZE],
48) -> Result<Vec<u8>>
49where
50    D1: AsRef<[u8]>,
51    D2: AsRef<[u8]>,
52{
53    let cipher = ChaCha20Poly1305::new(key.into());
54    let mut buffer = ciphertext.as_ref().to_vec();
55    cipher.decrypt_in_place_detached(
56        nonce.into(),
57        aad.as_ref(),
58        &mut buffer,
59        auth.into(),
60    )?;
61    Ok(buffer)
62}
63
64/// Symmetrically decrypts the given ciphertext using ChaCha20-Poly1305.
65///
66/// Returns the plaintext, or an error if the decryption failed.
67pub fn aead_chacha20_poly1305_decrypt<D>(
68    ciphertext: D,
69    key: &[u8; SYMMETRIC_KEY_SIZE],
70    nonce: &[u8; SYMMETRIC_NONCE_SIZE],
71    auth: &[u8; SYMMETRIC_AUTH_SIZE],
72) -> Result<Vec<u8>>
73where
74    D: AsRef<[u8]>,
75{
76    aead_chacha20_poly1305_decrypt_with_aad(ciphertext, key, nonce, [], auth)
77}
78
79#[cfg(test)]
80mod tests {
81    use bc_rand::random_data;
82    use hex_literal::hex;
83
84    use super::{
85        aead_chacha20_poly1305_decrypt_with_aad,
86        aead_chacha20_poly1305_encrypt_with_aad,
87    };
88    use crate::SYMMETRIC_AUTH_SIZE;
89
90    const PLAINTEXT: &[u8] = b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
91    const AAD: [u8; 12] = hex!("50515253c0c1c2c3c4c5c6c7");
92    const KEY: [u8; 32] = hex!(
93        "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
94    );
95    const NONCE: [u8; 12] = hex!("070000004041424344454647");
96    const CIPHERTEXT: [u8; 114] = hex!(
97        "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116"
98    );
99    const AUTH: [u8; 16] = hex!("1ae10b594f09e26a7e902ecbd0600691");
100
101    fn encrypted() -> (Vec<u8>, [u8; SYMMETRIC_AUTH_SIZE]) {
102        aead_chacha20_poly1305_encrypt_with_aad(PLAINTEXT, &KEY, &NONCE, AAD)
103    }
104
105    #[test]
106    fn test_rfc_test_vector() {
107        let (ciphertext, auth) = encrypted();
108        assert_eq!(ciphertext, CIPHERTEXT);
109        assert_eq!(auth, AUTH);
110
111        let decrypted_plaintext = aead_chacha20_poly1305_decrypt_with_aad(
112            &ciphertext,
113            &KEY,
114            &NONCE,
115            AAD,
116            &auth,
117        )
118        .unwrap();
119        assert_eq!(PLAINTEXT, decrypted_plaintext.as_slice());
120    }
121
122    #[test]
123    fn test_random_key_and_nonce() {
124        let key = random_data(32).try_into().unwrap();
125        let nonce = random_data(12).try_into().unwrap();
126        let (ciphertext, auth) = aead_chacha20_poly1305_encrypt_with_aad(
127            PLAINTEXT, &key, &nonce, AAD,
128        );
129        let decrypted_plaintext = aead_chacha20_poly1305_decrypt_with_aad(
130            ciphertext, &key, &nonce, AAD, &auth,
131        )
132        .unwrap();
133        assert_eq!(PLAINTEXT, decrypted_plaintext.as_slice());
134    }
135
136    #[test]
137    fn test_empty_data() {
138        let key = random_data(32).try_into().unwrap();
139        let nonce = random_data(12).try_into().unwrap();
140        let (ciphertext, auth) =
141            aead_chacha20_poly1305_encrypt_with_aad([], &key, &nonce, []);
142        let decrypted_plaintext = aead_chacha20_poly1305_decrypt_with_aad(
143            ciphertext,
144            &key,
145            &nonce,
146            [],
147            &auth,
148        )
149        .unwrap();
150        assert_eq!(Vec::<u8>::new(), decrypted_plaintext.as_slice());
151    }
152}