aescrypt_rs/decryptor/stream/
versions.rs

1//! src/decryptor/stream/versions.rs
2//! Final version — matches original working code exactly
3//! All tests pass, zero warnings, secure-gate everywhere
4
5use crate::aliases::{Aes256Key, Iv16};
6use crate::decryptor::stream::context::DecryptionContext;
7use crate::decryptor::stream::utils::{
8    extract_hmac_scattered, extract_hmac_simple, write_final_modulo, write_final_pkcs7,
9};
10use crate::error::AescryptError;
11use aes::cipher::KeyInit;
12use aes::Aes256Dec;
13use hmac::{Hmac, Mac};
14use sha2::Sha256;
15use std::io::{Read, Write};
16
17type HmacSha256 = Hmac<Sha256>;
18
19#[derive(Clone, Copy)]
20pub enum StreamConfig {
21    V0 { reserved_modulo: u8 },
22    V1,
23    V2,
24    V3,
25}
26
27#[inline(always)]
28pub fn decrypt_ciphertext_stream<R, W>(
29    mut input_reader: R,
30    mut output_writer: W,
31    initial_vector: &Iv16,
32    encryption_key: &Aes256Key,
33    config: StreamConfig,
34) -> Result<(), AescryptError>
35where
36    R: Read,
37    W: Write,
38{
39    let key_bytes = encryption_key.expose_secret();
40    let cipher = Aes256Dec::new(key_bytes.into());
41
42    // This is the exact same construction used in encrypt_stream
43    let mut hmac = <HmacSha256 as Mac>::new_from_slice(key_bytes)
44        .expect("encryption_key is always 32 bytes — valid HMAC key");
45
46    let mut ctx = DecryptionContext::new_with_iv(initial_vector);
47    ctx.decrypt_cbc_loop(&mut input_reader, &mut output_writer, &cipher, &mut hmac)?;
48
49    ctx.advance_tail();
50    let remaining = ctx.remaining();
51
52    match config {
53        StreamConfig::V0 { reserved_modulo } => {
54            if remaining != 32 {
55                return Err(AescryptError::Header(
56                    "v0: expected 32-byte HMAC trailer".into(),
57                ));
58            }
59
60            let expected_hmac = extract_hmac_simple(&ctx);
61
62            if hmac.finalize().into_bytes().as_slice() != expected_hmac {
63                return Err(AescryptError::Header("HMAC verification failed".into()));
64            }
65
66            write_final_modulo(&ctx, &mut output_writer, reserved_modulo)?;
67        }
68
69        StreamConfig::V1 | StreamConfig::V2 => {
70            if remaining != 33 {
71                return Err(AescryptError::Header(
72                    "v1/v2: expected 33-byte trailer".into(),
73                ));
74            }
75
76            let (expected_hmac, modulo_byte) = extract_hmac_scattered(&ctx);
77
78            if hmac.finalize().into_bytes().as_slice() != expected_hmac {
79                return Err(AescryptError::Header("HMAC verification failed".into()));
80            }
81
82            write_final_modulo(&ctx, &mut output_writer, modulo_byte)?;
83        }
84
85        StreamConfig::V3 => {
86            if remaining != 32 {
87                return Err(AescryptError::Header(
88                    "v3: expected 32-byte HMAC trailer".into(),
89                ));
90            }
91
92            let expected_hmac = extract_hmac_simple(&ctx);
93
94            if hmac.finalize().into_bytes().as_slice() != expected_hmac {
95                return Err(AescryptError::Header("HMAC verification failed".into()));
96            }
97
98            write_final_pkcs7(&ctx, &mut output_writer)?;
99        }
100    }
101
102    Ok(())
103}