1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//! src/core/decryption/decrypt.rs
//! High-level [`decrypt`] entry point: streams an AES Crypt v0–v3 file to plaintext.
use crate;
use crateextract_session_data;
use crate;
use crate;
use crateAescryptError;
use crate::;
use ;
/// Decrypts an AES Crypt v0–v3 file streamed from `input` and writes the
/// recovered plaintext to `output`.
///
/// `decrypt` parses the header (auto-detecting the format version), derives
/// the setup key (PBKDF2-HMAC-SHA512 for v3, ACKDF-SHA-256 for v0/v1/v2),
/// recovers the session IV/key, and runs the version-appropriate streaming
/// CBC loop. Only the version dispatched at the top of the function is
/// observable on output — the internal `StreamConfig` mapping is mediated by
/// [`crate::decryption::decrypt_ciphertext_stream`].
///
/// # Format
///
/// - v0: legacy modulo padding, 32-byte trailing HMAC, ACKDF setup key.
/// - v1/v2: scattered modulo + 32-byte HMAC, ACKDF setup key.
/// - v3: PKCS#7 padding, 32-byte HMAC, PBKDF2-HMAC-SHA512 setup key, version
/// byte folded into the session HMAC.
///
/// See [`crate::decryption`] for the full per-stage compatibility matrix.
///
/// # Errors
///
/// - [`AescryptError::Io`] — `input.read` or `output.write_all` returned an
/// I/O error.
/// - [`AescryptError::Header`] — invalid magic, non-zero v1–v3 reserved byte,
/// too many extensions, malformed iteration count, session-HMAC mismatch
/// (`"session data corrupted or tampered"`), payload-HMAC mismatch
/// (`"HMAC verification failed"`), or invalid v3 PKCS#7 padding.
/// - [`AescryptError::UnsupportedVersion`] — version byte is `> 3`.
/// - [`AescryptError::Crypto`] — KDF failure (PBKDF2 or ACKDF, including
/// non-UTF-8 password bytes for v0–v2).
///
/// # Panics
///
/// Never panics on valid or malformed input. The internal `unreachable!` is
/// guarded by [`crate::decryption::read_file_version`], which clamps the
/// version to `0..=3`.
///
/// # Security
///
/// **Decrypt-then-verify**. For payloads larger than roughly two AES blocks
/// (≈32 bytes of ciphertext after the session block), decrypted data is
/// written to `output` **incrementally** as blocks are processed. The payload
/// HMAC and v3 PKCS#7 validation run only after the ciphertext stream has been
/// read.
///
/// If this function returns an error — for example
/// `"HMAC verification failed"` or a v3 padding error — `output` may already
/// contain **partial, unauthenticated plaintext**. Callers **must** discard or
/// overwrite `output` on error and must not treat its contents as secret or
/// trustworthy.
///
/// ```no_run
/// use aescrypt_rs::{decrypt, PasswordString};
/// use std::io::Cursor;
///
/// # let reader = Cursor::new(vec![]);
/// # let password = PasswordString::new("pw".to_string());
/// let mut plaintext = Vec::new();
/// if decrypt(reader, &mut plaintext, &password).is_err() {
/// plaintext.clear(); // mandatory when using an accumulating buffer
/// }
/// ```
///
/// Other security properties:
///
/// - Setup, session, and intermediate keys live in [`secure-gate`] aliases and
/// zeroize on drop. The [`PasswordString`] never appears in plain form
/// outside scoped reveals.
/// - HMAC and PKCS#7 padding are compared in constant time
/// (`secure-gate`'s `ConstantTimeEq`).
/// - Pre-authentication parsing is bounded: the header has fixed sizes,
/// extensions are capped at 256 entries, and the iteration count is clamped
/// to [`PBKDF2_MAX_ITER`](crate::constants::PBKDF2_MAX_ITER).
///
/// # Compatibility — empty password
///
/// Unlike [`crate::encrypt()`], this function does not reject an empty
/// password. An empty password against a file encrypted with a non-empty
/// password will fail at HMAC verification. This asymmetry is intentional:
/// third-party AES Crypt tools may produce files encrypted with an empty
/// password, and `decrypt` must be able to handle them.
///
/// # Thread Safety
///
/// `decrypt` is `Send` whenever its `R`/`W` are. There is no shared mutable
/// state, so multiple threads may call `decrypt` concurrently on independent
/// inputs/outputs.
///
/// # Examples
///
/// ```no_run
/// use aescrypt_rs::{decrypt, PasswordString};
/// use std::io::Cursor;
///
/// let password = PasswordString::new("secret".to_string());
/// let ciphertext: &[u8] = b""; // contents of a .aes file
///
/// let mut plaintext = Vec::new();
/// match decrypt(Cursor::new(ciphertext), &mut plaintext, &password) {
/// Ok(()) => { /* plaintext is now authenticated */ }
/// Err(_) => plaintext.clear(),
/// }
/// ```
///
/// Threaded usage:
///
/// ```no_run
/// use aescrypt_rs::{decrypt, PasswordString};
/// use std::io::Cursor;
/// use std::thread;
///
/// let password = PasswordString::new("secret".to_string());
/// let encrypted = b"encrypted data...";
///
/// let handle = thread::spawn(move || {
/// let mut plaintext = Vec::new();
/// decrypt(Cursor::new(encrypted), &mut plaintext, &password)
/// });
///
/// let _result = handle.join().unwrap();
/// ```
///
/// # See also
///
/// - [`crate::encrypt()`] — inverse operation.
/// - [`crate::read_version`] — header-only version triage.
///
/// [`secure-gate`]: https://github.com/Slurp9187/secure-gate
/// [`PasswordString`]: crate::PasswordString