Skip to main content

crypto/chacha/
chacha8_poly1305.rs

1use crate::{
2    Aead, AeadError, Bytes, Hash, StreamCipher,
3    chacha::{ChaCha, chacha20_poly1305::update_poly1305_padded},
4    poly1305::Poly1305,
5};
6
7/// The ChaCha8-Poly1305 AEAD, derived from ChaCha20-Poly1305 as standardized in RFC 8439
8/// but with a reduced number of ChaCha rounds for embedded platforms.
9///
10/// # Parameters
11///
12/// - Key: 256 bits (32 bytes)
13/// - Nonce: 96 bits (12 bytes)
14/// - Tag: 128 bits (16 bytes)
15///
16/// # Example
17///
18/// ```
19/// use crypto::{Aead, chacha::ChaCha8Poly1305};
20///
21/// let key = [0x55; 32];
22/// let nonce = [0xaa; 12];
23/// let aead = ChaCha8Poly1305::new(&key);
24///
25/// let aad = b"authenticated but not encrypted";
26/// let plaintext = b"hello, world!";
27/// let mut buf = plaintext.to_vec();
28///
29/// let tag = aead.encrypt_in_place(&mut buf, &nonce, aad);
30/// let result = aead.decrypt_in_place(&mut buf, &nonce, aad, tag.as_ref());
31/// assert!(result.is_ok());
32/// assert_eq!(&buf, plaintext);
33/// ```
34#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
35pub struct ChaCha8Poly1305 {
36    key: [u8; 32],
37}
38
39impl ChaCha8Poly1305 {
40    /// Creates a new AEAD instance from a 32-byte key.
41    pub fn new(key: &[u8; 32]) -> ChaCha8Poly1305 {
42        return ChaCha8Poly1305 {
43            key: *key,
44        };
45    }
46
47    /// Generates the one-time Poly1305 key using ChaCha20 with counter=0.
48    #[inline]
49    fn poly1305_key_gen(&self, nonce: &[u8; 12]) -> ([u8; 32], ChaCha<8, true>) {
50        let mut cipher = ChaCha::<8, true>::new(&self.key, nonce);
51        cipher.set_counter(0);
52        let mut block = [0u8; 64];
53        cipher.xor_keystream(&mut block);
54        let mut key = [0u8; 32];
55        key.copy_from_slice(&block[..32]);
56        return (key, cipher);
57    }
58}
59
60impl Aead for ChaCha8Poly1305 {
61    const TAG_SIZE: usize = 16;
62    const NONCE_SIZE: usize = 12;
63
64    fn encrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8]) -> Hash {
65        let nonce: &[u8; 12] = nonce.try_into().expect("nonce must be 12 bytes");
66        let (poly1305key, mut cipher) = self.poly1305_key_gen(nonce);
67
68        cipher.set_counter(1);
69        cipher.xor_keystream(in_out);
70
71        let mut mac = Poly1305::new(&poly1305key);
72        update_poly1305_padded(&mut mac, aad);
73        update_poly1305_padded(&mut mac, in_out);
74        mac.update(&(aad.len() as u64).to_le_bytes());
75        mac.update(&(in_out.len() as u64).to_le_bytes());
76        let tag_bytes = mac.finalize();
77
78        let mut tag = Hash(Bytes::<64>::with_length(16));
79        tag.as_mut().copy_from_slice(&tag_bytes);
80        return tag;
81    }
82
83    fn decrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8], tag: &[u8]) -> Result<(), AeadError> {
84        if tag.len() != Self::TAG_SIZE {
85            return Err(AeadError::InvalidCiphertext);
86        }
87        let nonce: &[u8; 12] = nonce.try_into().map_err(|_| AeadError::InvalidNonce)?;
88        let (poly1305key, mut cipher) = self.poly1305_key_gen(nonce);
89
90        let mut mac = Poly1305::new(&poly1305key);
91        update_poly1305_padded(&mut mac, aad);
92        update_poly1305_padded(&mut mac, in_out);
93        mac.update(&(aad.len() as u64).to_le_bytes());
94        mac.update(&(in_out.len() as u64).to_le_bytes());
95        let computed = mac.finalize();
96
97        if !constant_time_eq::constant_time_eq(&computed, tag) {
98            return Err(AeadError::InvalidCiphertext);
99        }
100
101        cipher.set_counter(1);
102        cipher.xor_keystream(in_out);
103
104        return Ok(());
105    }
106}
107
108#[cfg(test)]
109mod test {
110    use super::*;
111
112    #[test]
113    fn aead_roundtrip() {
114        let key = [0x55; 32];
115        let nonce = [0xaa; 12];
116        let aad = b"authenticated but not encrypted";
117        let aead = ChaCha8Poly1305::new(&key);
118        let plaintext = b"hello, world!";
119        let mut buf = plaintext.to_vec();
120        let tag = aead.encrypt_in_place(&mut buf, &nonce, aad);
121        let result = aead.decrypt_in_place(&mut buf, &nonce, aad, tag.as_ref());
122        assert!(result.is_ok());
123        assert_eq!(&buf, plaintext);
124    }
125}