Skip to main content

chacha20poly1305/
cipher.rs

1//! Core AEAD cipher implementation for (X)ChaCha20Poly1305.
2
3use ::cipher::{StreamCipher, StreamCipherSeek};
4use aead::Error;
5use aead::{array::Array, inout::InOutBuf};
6use poly1305::{
7    Poly1305,
8    universal_hash::{KeyInit, UniversalHash},
9};
10
11use super::Tag;
12
13/// Size of a ChaCha20 block in bytes
14const BLOCK_SIZE: usize = 64;
15
16/// Maximum number of blocks that can be encrypted with ChaCha20 before the
17/// counter overflows.
18const MAX_BLOCKS: usize = u32::MAX as usize;
19
20/// ChaCha20Poly1305 instantiated with a particular nonce
21pub(crate) struct Cipher<C>
22where
23    C: StreamCipher + StreamCipherSeek,
24{
25    cipher: C,
26    mac: Poly1305,
27}
28
29impl<C> Cipher<C>
30where
31    C: StreamCipher + StreamCipherSeek,
32{
33    /// Instantiate the underlying cipher with a particular nonce
34    pub(crate) fn new(mut cipher: C) -> Self {
35        // Derive Poly1305 key from the first 32-bytes of the ChaCha20 keystream
36        let mut mac_key = poly1305::Key::default();
37        cipher.apply_keystream(&mut mac_key);
38
39        let mac = Poly1305::new(&mac_key);
40        #[cfg(feature = "zeroize")]
41        {
42            use zeroize::Zeroize;
43            mac_key.zeroize();
44        }
45
46        // Set ChaCha20 counter to 1
47        cipher.seek(BLOCK_SIZE as u64);
48
49        Self { cipher, mac }
50    }
51
52    /// Encrypt the given message in-place, returning the authentication tag
53    pub(crate) fn encrypt_inout_detached(
54        mut self,
55        associated_data: &[u8],
56        mut buffer: InOutBuf<'_, '_, u8>,
57    ) -> Result<Tag, Error> {
58        if buffer.len() / BLOCK_SIZE >= MAX_BLOCKS {
59            return Err(Error);
60        }
61
62        self.mac.update_padded(associated_data);
63
64        // TODO(tarcieri): interleave encryption with Poly1305
65        // See: <https://github.com/RustCrypto/AEADs/issues/74>
66        self.cipher.apply_keystream_inout(buffer.reborrow());
67        self.mac.update_padded(buffer.get_out());
68
69        self.authenticate_lengths(associated_data, buffer.get_out())?;
70        Ok(self.mac.finalize())
71    }
72
73    /// Decrypt the given message, first authenticating ciphertext integrity
74    /// and returning an error if it's been tampered with.
75    pub(crate) fn decrypt_inout_detached(
76        mut self,
77        associated_data: &[u8],
78        buffer: InOutBuf<'_, '_, u8>,
79        tag: &Tag,
80    ) -> Result<(), Error> {
81        if buffer.len() / BLOCK_SIZE >= MAX_BLOCKS {
82            return Err(Error);
83        }
84
85        self.mac.update_padded(associated_data);
86        self.mac.update_padded(buffer.get_in());
87        self.authenticate_lengths(associated_data, buffer.get_in())?;
88
89        // This performs a constant-time comparison using the `subtle` crate
90        if self.mac.verify(tag).is_ok() {
91            // TODO(tarcieri): interleave decryption with Poly1305
92            // See: <https://github.com/RustCrypto/AEADs/issues/74>
93            self.cipher.apply_keystream_inout(buffer);
94            Ok(())
95        } else {
96            Err(Error)
97        }
98    }
99
100    /// Authenticate the lengths of the associated data and message
101    fn authenticate_lengths(&mut self, associated_data: &[u8], buffer: &[u8]) -> Result<(), Error> {
102        let associated_data_len: u64 = associated_data.len().try_into().map_err(|_| Error)?;
103        let buffer_len: u64 = buffer.len().try_into().map_err(|_| Error)?;
104
105        let mut block = Array::default();
106        block[..8].copy_from_slice(&associated_data_len.to_le_bytes());
107        block[8..].copy_from_slice(&buffer_len.to_le_bytes());
108        self.mac.update(&[block]);
109
110        Ok(())
111    }
112}