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}