chacha20poly1305/
cipher.rs

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