dcrypt_symmetric/streaming/
chacha20poly1305.rs

1//! Streaming ChaCha20Poly1305 implementations
2
3use crate::aead::chacha20poly1305::{
4    ChaCha20Poly1305Cipher, ChaCha20Poly1305Key, ChaCha20Poly1305Nonce,
5};
6use crate::cipher::{Aead, SymmetricCipher};
7use crate::error::{validate_stream_state, Result, SymmetricResultExt};
8use crate::streaming::{StreamingDecrypt, StreamingEncrypt};
9use std::io::{Read, Write};
10
11/// Streaming encryption API for ChaCha20Poly1305 with secure nonce management
12pub struct ChaCha20Poly1305EncryptStream<W: Write> {
13    writer: W,
14    cipher: ChaCha20Poly1305Cipher,
15    buffer: Vec<u8>,
16    finalized: bool,
17    aad: Option<Vec<u8>>,
18    // Counter for deriving unique nonces
19    counter: u32,
20    // Base nonce - used to derive per-chunk nonces
21    base_nonce: ChaCha20Poly1305Nonce,
22}
23
24impl<W: Write> ChaCha20Poly1305EncryptStream<W> {
25    /// Creates a new encryption stream
26    pub fn new(writer: W, key: &ChaCha20Poly1305Key, aad: Option<&[u8]>) -> Result<Self> {
27        // Create cipher with proper error handling
28        let cipher = ChaCha20Poly1305Cipher::new(key)?;
29        let base_nonce = ChaCha20Poly1305Cipher::generate_nonce();
30
31        // Write base nonce to the beginning of the stream
32        let mut w = writer;
33        // Use the new error conversion trait
34        w.write_all(base_nonce.as_bytes()).map_io_err()?;
35
36        Ok(Self {
37            writer: w,
38            cipher,
39            buffer: Vec::with_capacity(16384), // 16 KB buffer
40            finalized: false,
41            aad: aad.map(|a| a.to_vec()),
42            counter: 0,
43            base_nonce,
44        })
45    }
46
47    /// Derives a unique nonce for the current chunk
48    fn derive_chunk_nonce(&self) -> ChaCha20Poly1305Nonce {
49        // Create a derived nonce by XORing the counter with the base nonce
50        let mut nonce_bytes = *self.base_nonce.as_bytes();
51        let counter_bytes = self.counter.to_be_bytes();
52
53        // XOR the last 4 bytes with the counter
54        for i in 0..4 {
55            nonce_bytes[8 + i] ^= counter_bytes[i];
56        }
57
58        ChaCha20Poly1305Nonce::new(nonce_bytes)
59    }
60
61    /// Flushes the internal buffer, encrypting and writing data
62    fn flush_buffer(&mut self) -> Result<()> {
63        if self.buffer.is_empty() {
64            return Ok(());
65        }
66
67        // Generate a unique nonce for this chunk using counter
68        let chunk_nonce = self.derive_chunk_nonce();
69
70        // Encrypt the buffered data with the unique nonce
71        let ciphertext = self
72            .cipher
73            .encrypt(&chunk_nonce, &self.buffer, self.aad.as_deref())?;
74
75        // Write the chunk nonce indicator followed by ciphertext length and data
76        self.writer.write_all(&[1]).map_io_err()?; // 1 = has chunk nonce
77
78        // Write the chunk counter (used to derive the nonce)
79        let counter_bytes = self.counter.to_be_bytes();
80        self.writer.write_all(&counter_bytes).map_io_err()?;
81
82        // Write the length of the ciphertext followed by the ciphertext itself
83        let len = (ciphertext.len() as u32).to_be_bytes();
84        self.writer.write_all(&len).map_io_err()?;
85        self.writer.write_all(&ciphertext).map_io_err()?;
86
87        // Increment counter for next chunk
88        self.counter += 1;
89
90        // Clear the buffer
91        self.buffer.clear();
92
93        Ok(())
94    }
95}
96
97impl<W: Write> StreamingEncrypt<W> for ChaCha20Poly1305EncryptStream<W> {
98    /// Writes plaintext data to the stream
99    fn write(&mut self, data: &[u8]) -> Result<()> {
100        validate_stream_state(!self.finalized, "stream write", "stream already finalized")?;
101
102        // Add data to internal buffer
103        self.buffer.extend_from_slice(data);
104
105        // If buffer exceeds 16 KB, encrypt and write a chunk
106        if self.buffer.len() >= 16384 {
107            self.flush_buffer()?;
108        }
109
110        Ok(())
111    }
112
113    /// Finalizes the stream, encrypting any remaining data
114    fn finalize(mut self) -> Result<W> {
115        validate_stream_state(
116            !self.finalized,
117            "stream finalize",
118            "stream already finalized",
119        )?;
120
121        // Flush any remaining data
122        self.flush_buffer()?;
123
124        // Write a zero marker to indicate end of data
125        self.writer.write_all(&[0]).map_io_err()?;
126
127        self.finalized = true;
128        Ok(self.writer)
129    }
130}
131
132/// Streaming decryption API for ChaCha20Poly1305 with secure nonce handling
133pub struct ChaCha20Poly1305DecryptStream<R: Read> {
134    reader: R,
135    cipher: ChaCha20Poly1305Cipher,
136    base_nonce: ChaCha20Poly1305Nonce,
137    finished: bool,
138    aad: Option<Vec<u8>>,
139}
140
141impl<R: Read> ChaCha20Poly1305DecryptStream<R> {
142    /// Creates a new decryption stream
143    pub fn new(mut reader: R, key: &ChaCha20Poly1305Key, aad: Option<&[u8]>) -> Result<Self> {
144        // Read the base nonce from the beginning of the stream
145        let mut nonce_bytes = [0u8; 12];
146        reader.read_exact(&mut nonce_bytes).map_io_err()?;
147
148        let base_nonce = ChaCha20Poly1305Nonce::new(nonce_bytes);
149        // Create cipher with proper error handling
150        let cipher = ChaCha20Poly1305Cipher::new(key)?;
151
152        Ok(Self {
153            reader,
154            cipher,
155            base_nonce,
156            finished: false,
157            aad: aad.map(|a| a.to_vec()),
158        })
159    }
160
161    /// Derives a chunk nonce from the base nonce and counter
162    fn derive_chunk_nonce(&self, counter: u32) -> ChaCha20Poly1305Nonce {
163        let mut nonce_bytes = *self.base_nonce.as_bytes();
164        let counter_bytes = counter.to_be_bytes();
165
166        // XOR the last 4 bytes with the counter
167        for i in 0..4 {
168            nonce_bytes[8 + i] ^= counter_bytes[i];
169        }
170
171        ChaCha20Poly1305Nonce::new(nonce_bytes)
172    }
173}
174
175impl<R: Read> StreamingDecrypt<R> for ChaCha20Poly1305DecryptStream<R> {
176    /// Reads and decrypts data from the stream
177    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
178        if self.finished {
179            return Ok(0);
180        }
181
182        // Read the chunk marker
183        let mut marker = [0u8; 1];
184        self.reader.read_exact(&mut marker).map_io_err()?;
185
186        // Check if we've reached the end of the stream
187        if marker[0] == 0 {
188            self.finished = true;
189            return Ok(0);
190        }
191
192        // Read the chunk counter
193        let mut counter_bytes = [0u8; 4];
194        self.reader.read_exact(&mut counter_bytes).map_io_err()?;
195        let counter = u32::from_be_bytes(counter_bytes);
196
197        // Derive the nonce for this chunk
198        let chunk_nonce = self.derive_chunk_nonce(counter);
199
200        // Read the length of the ciphertext
201        let mut len_bytes = [0u8; 4];
202        self.reader.read_exact(&mut len_bytes).map_io_err()?;
203        let len = u32::from_be_bytes(len_bytes) as usize;
204
205        // Read the ciphertext
206        let mut ciphertext = vec![0u8; len];
207        self.reader.read_exact(&mut ciphertext).map_io_err()?;
208
209        // Decrypt the chunk using the derived nonce
210        let plaintext = self
211            .cipher
212            .decrypt(&chunk_nonce, &ciphertext, self.aad.as_deref())?;
213
214        // Copy to output buffer
215        let to_copy = plaintext.len().min(buf.len());
216        buf[..to_copy].copy_from_slice(&plaintext[..to_copy]);
217
218        Ok(to_copy)
219    }
220}
221
222/// Encrypts a file using ChaCha20Poly1305
223pub fn encrypt_file<R: Read, W: Write>(
224    mut reader: R,
225    writer: W,
226    key: &ChaCha20Poly1305Key,
227    aad: Option<&[u8]>,
228) -> Result<()> {
229    // Create stream with proper error handling
230    let mut stream = ChaCha20Poly1305EncryptStream::new(writer, key, aad)?;
231
232    let mut buffer = [0u8; 8192];
233    loop {
234        let bytes_read = reader.read(&mut buffer).map_io_err()?;
235        if bytes_read == 0 {
236            break;
237        }
238
239        stream.write(&buffer[..bytes_read])?;
240    }
241
242    stream.finalize()?;
243    Ok(())
244}
245
246/// Decrypts a file using ChaCha20Poly1305
247pub fn decrypt_file<R: Read, W: Write>(
248    reader: R,
249    mut writer: W,
250    key: &ChaCha20Poly1305Key,
251    aad: Option<&[u8]>,
252) -> Result<()> {
253    let mut stream = ChaCha20Poly1305DecryptStream::new(reader, key, aad)?;
254
255    let mut buffer = [0u8; 8192];
256    loop {
257        let bytes_read = stream.read(&mut buffer)?;
258        if bytes_read == 0 {
259            break;
260        }
261
262        writer.write_all(&buffer[..bytes_read]).map_io_err()?;
263    }
264
265    Ok(())
266}