dexios_core/
stream.rs

1//! This module contains all of the LE31 STREAM objects and functionality
2//!
3//! This is where streaming mode encryption, decryption and initialization is handled.
4//!
5//! There are also some convenience functions for quickly encrypting and decrypting files.
6//!
7//! # Examples
8//!
9//! ```rust,ignore
10//! // obviously the key should contain data, not be an empty vec
11//! let raw_key = Protected::new(vec![0u8; 128]);
12//! let salt = gen_salt();
13//! let key = balloon_hash(raw_key, &salt, &HeaderVersion::V4).unwrap();
14//!
15//! // this nonce should be read from somewhere, not generated
16//! let nonce = gen_nonce(&Algorithm::XChaCha20Poly1305, &Mode::StreamMode);
17//!
18//! let decrypt_stream = DecryptionStreams::initialize(key, &nonce, &Algorithm::XChaCha20Poly1305).unwrap();
19//!
20//! let mut input_file = File::open("input.encrypted").unwrap();
21//! let mut output_file = File::create("output").unwrap();
22//!
23//! // aad should be retrieved from the `Header` (with `Header::deserialize()`)
24//! let aad = Vec::new();
25//!
26//! decrypt_stream.decrypt_file(&mut input_file, &mut output_file, &aad);
27//! ```
28
29use std::io::{Read, Write};
30
31use aead::{
32    stream::{DecryptorLE31, EncryptorLE31},
33    KeyInit, Payload,
34};
35use aes_gcm::Aes256Gcm;
36use anyhow::Context;
37use chacha20poly1305::XChaCha20Poly1305;
38use deoxys::DeoxysII256;
39// use rand::{prelude::StdRng, Rng, SeedableRng, RngCore};
40use zeroize::Zeroize;
41
42use crate::primitives::{Algorithm, BLOCK_SIZE};
43use crate::protected::Protected;
44
45/// This `enum` contains streams for that are used solely for encryption
46///
47/// It has definitions for all AEADs supported by `dexios-core`
48pub enum EncryptionStreams {
49    Aes256Gcm(Box<EncryptorLE31<Aes256Gcm>>),
50    XChaCha20Poly1305(Box<EncryptorLE31<XChaCha20Poly1305>>),
51    DeoxysII256(Box<EncryptorLE31<DeoxysII256>>),
52}
53
54/// This `enum` contains streams for that are used solely for decryption
55///
56/// It has definitions for all AEADs supported by `dexios-core`
57pub enum DecryptionStreams {
58    Aes256Gcm(Box<DecryptorLE31<Aes256Gcm>>),
59    XChaCha20Poly1305(Box<DecryptorLE31<XChaCha20Poly1305>>),
60    DeoxysII256(Box<DecryptorLE31<DeoxysII256>>),
61}
62
63impl EncryptionStreams {
64    /// This method can be used to quickly create an `EncryptionStreams` object
65    ///
66    /// It requies a 32-byte hashed key, which will be dropped once the stream has been initialized
67    ///
68    /// It requires a pre-generated nonce, which you may generate with `gen_nonce()`
69    ///
70    /// If the nonce length is not exact, you will receive an error.
71    ///
72    /// It will create the stream with the specified algorithm, and it will also generate the appropriate nonce
73    ///
74    /// The `EncryptionStreams` object is returned
75    ///
76    /// # Examples
77    ///
78    /// ```rust,ignore
79    /// // obviously the key should contain data, not be an empty vec
80    /// let raw_key = Protected::new(vec![0u8; 128]);
81    /// let salt = gen_salt();
82    /// let key = balloon_hash(raw_key, &salt, &HeaderVersion::V4).unwrap();
83    ///
84    /// let nonce = gen_nonce(&Algorithm::XChaCha20Poly1305, &Mode::StreamMode);
85    /// let encrypt_stream = EncryptionStreams::initialize(key, &nonce, &Algorithm::XChaCha20Poly1305).unwrap();
86    /// ```
87    ///
88    pub fn initialize(
89        key: Protected<[u8; 32]>,
90        nonce: &[u8],
91        algorithm: &Algorithm,
92    ) -> anyhow::Result<Self> {
93        let streams = match algorithm {
94            Algorithm::Aes256Gcm => {
95                if nonce.len() != 8 {
96                    return Err(anyhow::anyhow!("Nonce is not the correct length"));
97                }
98
99                let cipher = Aes256Gcm::new_from_slice(key.expose())
100                    .map_err(|_| anyhow::anyhow!("Unable to create cipher with hashed key."))?;
101
102                let stream = EncryptorLE31::from_aead(cipher, nonce.into());
103                EncryptionStreams::Aes256Gcm(Box::new(stream))
104            }
105            Algorithm::XChaCha20Poly1305 => {
106                if nonce.len() != 20 {
107                    return Err(anyhow::anyhow!("Nonce is not the correct length"));
108                }
109
110                let cipher = XChaCha20Poly1305::new_from_slice(key.expose())
111                    .map_err(|_| anyhow::anyhow!("Unable to create cipher with hashed key."))?;
112
113                let stream = EncryptorLE31::from_aead(cipher, nonce.into());
114                EncryptionStreams::XChaCha20Poly1305(Box::new(stream))
115            }
116            Algorithm::DeoxysII256 => {
117                if nonce.len() != 11 {
118                    return Err(anyhow::anyhow!("Nonce is not the correct length"));
119                }
120
121                let cipher = DeoxysII256::new_from_slice(key.expose())
122                    .map_err(|_| anyhow::anyhow!("Unable to create cipher with hashed key."))?;
123
124                let stream = EncryptorLE31::from_aead(cipher, nonce.into());
125                EncryptionStreams::DeoxysII256(Box::new(stream))
126            }
127        };
128
129        drop(key);
130        Ok(streams)
131    }
132
133    /// This is used for encrypting the *next* block of data in streaming mode
134    ///
135    /// It requires either some plaintext, or an `aead::Payload` (that contains the plaintext and the AAD)
136    pub fn encrypt_next<'msg, 'aad>(
137        &mut self,
138        payload: impl Into<Payload<'msg, 'aad>>,
139    ) -> aead::Result<Vec<u8>> {
140        match self {
141            EncryptionStreams::Aes256Gcm(s) => s.encrypt_next(payload),
142            EncryptionStreams::XChaCha20Poly1305(s) => s.encrypt_next(payload),
143            EncryptionStreams::DeoxysII256(s) => s.encrypt_next(payload),
144        }
145    }
146
147    /// This is used for encrypting the *last* block of data in streaming mode. It consumes the stream object to prevent further usage.
148    ///
149    /// It requires either some plaintext, or an `aead::Payload` (that contains the plaintext and the AAD)
150    pub fn encrypt_last<'msg, 'aad>(
151        self,
152        payload: impl Into<Payload<'msg, 'aad>>,
153    ) -> aead::Result<Vec<u8>> {
154        match self {
155            EncryptionStreams::Aes256Gcm(s) => s.encrypt_last(payload),
156            EncryptionStreams::XChaCha20Poly1305(s) => s.encrypt_last(payload),
157            EncryptionStreams::DeoxysII256(s) => s.encrypt_last(payload),
158        }
159    }
160
161    /// This is a convenience function for reading from a reader, encrypting, and writing to the writer.
162    ///
163    /// Every single block is provided with the AAD
164    ///
165    /// Valid AAD must be provided if you are using `HeaderVersion::V3` and above. It must be empty if the `HeaderVersion` is lower.
166    ///
167    /// You are free to use a custom AAD, just ensure that it is present for decryption, or else you will receive an error.
168    ///
169    /// This does not handle writing the header.
170    ///
171    /// # Examples
172    ///
173    /// ```rust,ignore
174    /// let mut input_file = File::open("input").unwrap();
175    /// let mut output_file = File::create("output.encrypted").unwrap();
176    ///
177    /// // aad should be generated from the header (only for encryption)
178    /// let aad = header.serialize().unwrap();
179    ///
180    /// let encrypt_stream = EncryptionStreams::initialize(key, &nonce, &Algorithm::XChaCha20Poly1305).unwrap();
181    /// encrypt_stream.encrypt_file(&mut input_file, &mut output_file, &aad);
182    /// ```
183    ///
184    pub fn encrypt_file(
185        mut self,
186        reader: &mut impl Read,
187        writer: &mut impl Write,
188        aad: &[u8],
189    ) -> anyhow::Result<()> {
190        #[cfg(feature = "visual")]
191        let pb = crate::visual::create_spinner();
192
193        let mut read_buffer = vec![0u8; BLOCK_SIZE].into_boxed_slice();
194        loop {
195            let read_count = reader
196                .read(&mut read_buffer)
197                .context("Unable to read from the reader")?;
198            if read_count == BLOCK_SIZE {
199                // aad is just empty bytes normally
200                // create_aad returns empty bytes if the header isn't V3+
201                // this means we don't need to do anything special in regards to older versions
202                let payload = Payload {
203                    aad,
204                    msg: read_buffer.as_ref(),
205                };
206
207                let encrypted_data = self
208                    .encrypt_next(payload)
209                    .map_err(|_| anyhow::anyhow!("Unable to encrypt the data"))?;
210
211                writer
212                    .write_all(&encrypted_data)
213                    .context("Unable to write to the output")?;
214            } else {
215                // if we read something less than BLOCK_SIZE, and have hit the end of the file
216                let payload = Payload {
217                    aad,
218                    msg: &read_buffer[..read_count],
219                };
220
221                let encrypted_data = self
222                    .encrypt_last(payload)
223                    .map_err(|_| anyhow::anyhow!("Unable to encrypt the data"))?;
224
225                writer
226                    .write_all(&encrypted_data)
227                    .context("Unable to write to the output")?;
228                break;
229            }
230        }
231        read_buffer.zeroize();
232        writer.flush().context("Unable to flush the output")?;
233
234        #[cfg(feature = "visual")]
235        pb.finish_and_clear();
236
237        Ok(())
238    }
239}
240
241impl DecryptionStreams {
242    /// This method can be used to quickly create an `DecryptionStreams` object
243    ///
244    /// It requies a 32-byte hashed key, which will be dropped once the stream has been initialized
245    ///
246    /// It requires the same nonce that was returned upon initializing `EncryptionStreams`
247    ///
248    /// It will create the stream with the specified algorithm
249    ///
250    /// The `DecryptionStreams` object will be returned
251    ///
252    /// # Examples
253    ///
254    /// ```rust,ignore
255    /// // obviously the key should contain data, not be an empty vec
256    /// let raw_key = Protected::new(vec![0u8; 128]);
257    /// let salt = gen_salt();
258    /// let key = balloon_hash(raw_key, &salt, &HeaderVersion::V4).unwrap();
259    ///
260    /// // this nonce should be read from somewhere, not generated
261    /// let nonce = gen_nonce(&Algorithm::XChaCha20Poly1305, &Mode::StreamMode);
262    ///
263    /// let decrypt_stream = DecryptionStreams::initialize(key, &nonce, &Algorithm::XChaCha20Poly1305).unwrap();
264    /// ```
265    ///
266    pub fn initialize(
267        key: Protected<[u8; 32]>,
268        nonce: &[u8],
269        algorithm: &Algorithm,
270    ) -> anyhow::Result<Self> {
271        let streams = match algorithm {
272            Algorithm::Aes256Gcm => {
273                let cipher = Aes256Gcm::new_from_slice(key.expose())
274                    .map_err(|_| anyhow::anyhow!("Unable to create cipher with hashed key."))?;
275
276                let stream = DecryptorLE31::from_aead(cipher, nonce.into());
277                DecryptionStreams::Aes256Gcm(Box::new(stream))
278            }
279            Algorithm::XChaCha20Poly1305 => {
280                let cipher = XChaCha20Poly1305::new_from_slice(key.expose())
281                    .map_err(|_| anyhow::anyhow!("Unable to create cipher with hashed key."))?;
282
283                let stream = DecryptorLE31::from_aead(cipher, nonce.into());
284                DecryptionStreams::XChaCha20Poly1305(Box::new(stream))
285            }
286            Algorithm::DeoxysII256 => {
287                let cipher = DeoxysII256::new_from_slice(key.expose())
288                    .map_err(|_| anyhow::anyhow!("Unable to create cipher with hashed key."))?;
289
290                let stream = DecryptorLE31::from_aead(cipher, nonce.into());
291                DecryptionStreams::DeoxysII256(Box::new(stream))
292            }
293        };
294
295        drop(key);
296        Ok(streams)
297    }
298
299    /// This is used for decrypting the *next* block of data in streaming mode
300    ///
301    /// It requires either some plaintext, or an `aead::Payload` (that contains the plaintext and the AAD)
302    ///
303    /// Whatever you provided as AAD while encrypting must be present during decryption, or else you will receive an error.
304    pub fn decrypt_next<'msg, 'aad>(
305        &mut self,
306        payload: impl Into<Payload<'msg, 'aad>>,
307    ) -> aead::Result<Vec<u8>> {
308        match self {
309            DecryptionStreams::Aes256Gcm(s) => s.decrypt_next(payload),
310            DecryptionStreams::XChaCha20Poly1305(s) => s.decrypt_next(payload),
311            DecryptionStreams::DeoxysII256(s) => s.decrypt_next(payload),
312        }
313    }
314
315    /// This is used for decrypting the *last* block of data in streaming mode. It consumes the stream object to prevent further usage.
316    ///
317    /// It requires either some plaintext, or an `aead::Payload` (that contains the plaintext and the AAD)
318    ///
319    /// Whatever you provided as AAD while encrypting must be present during decryption, or else you will receive an error.
320    pub fn decrypt_last<'msg, 'aad>(
321        self,
322        payload: impl Into<Payload<'msg, 'aad>>,
323    ) -> aead::Result<Vec<u8>> {
324        match self {
325            DecryptionStreams::Aes256Gcm(s) => s.decrypt_last(payload),
326            DecryptionStreams::XChaCha20Poly1305(s) => s.decrypt_last(payload),
327            DecryptionStreams::DeoxysII256(s) => s.decrypt_last(payload),
328        }
329    }
330
331    /// This is a convenience function for reading from a reader, decrypting, and writing to the writer.
332    ///
333    /// Every single block is provided with the AAD
334    ///
335    /// Valid AAD must be provided if you are using `HeaderVersion::V3` and above. It must be empty if the `HeaderVersion` is lower. Whatever you provided as AAD while encrypting must be present during decryption, or else you will receive an error.
336    ///
337    /// This does not handle writing the header.
338    ///
339    /// # Examples
340    ///
341    /// ```rust,ignore
342    /// let mut input_file = File::open("input.encrypted").unwrap();
343    /// let mut output_file = File::create("output").unwrap();
344    ///
345    /// // aad should be retrieved from the `Header` (with `Header::deserialize()`)
346    /// let aad = Vec::new();
347    ///
348    /// let decrypt_stream = DecryptionStreams::initialize(key, &nonce, &Algorithm::XChaCha20Poly1305).unwrap();
349    /// decrypt_stream.decrypt_file(&mut input_file, &mut output_file, &aad);
350    /// ```
351    ///
352    pub fn decrypt_file(
353        mut self,
354        reader: &mut impl Read,
355        writer: &mut impl Write,
356        aad: &[u8],
357    ) -> anyhow::Result<()> {
358        #[cfg(feature = "visual")]
359        let pb = crate::visual::create_spinner();
360
361        let mut buffer = vec![0u8; BLOCK_SIZE + 16].into_boxed_slice();
362        loop {
363            let read_count = reader.read(&mut buffer)?;
364            if read_count == (BLOCK_SIZE + 16) {
365                let payload = Payload {
366                    aad,
367                    msg: buffer.as_ref(),
368                };
369
370                let mut decrypted_data = self.decrypt_next(payload).map_err(|_| {
371                    anyhow::anyhow!("Unable to decrypt the data. This means either: you're using the wrong key, this isn't an encrypted file, or the header has been tampered with.")
372                })?;
373
374                writer
375                    .write_all(&decrypted_data)
376                    .context("Unable to write to the output")?;
377
378                decrypted_data.zeroize();
379            } else {
380                // if we read something less than BLOCK_SIZE+16, and have hit the end of the file
381                let payload = Payload {
382                    aad,
383                    msg: &buffer[..read_count],
384                };
385
386                let mut decrypted_data = self.decrypt_last(payload).map_err(|_| {
387                    anyhow::anyhow!("Unable to decrypt the final block of data. This means either: you're using the wrong key, this isn't an encrypted file, or the header has been tampered with.")
388                })?;
389
390                writer
391                    .write_all(&decrypted_data)
392                    .context("Unable to write to the output file")?;
393
394                decrypted_data.zeroize();
395                break;
396            }
397        }
398
399        writer.flush().context("Unable to flush the output")?;
400
401        #[cfg(feature = "visual")]
402        pb.finish_and_clear();
403
404        Ok(())
405    }
406}