aescrypt_rs/encryption/encrypt.rs
1// src/encryption/encrypt.rs
2
3//! High-level [`encrypt`] entry point: streams plaintext to an AES Crypt v3 file.
4
5use crate::aliases::PasswordString;
6use crate::aliases::{Aes256Key32, EncryptedSessionBlock48, HmacSha256, Iv16};
7use crate::constants::AESCRYPT_LATEST_VERSION;
8use crate::constants::{PBKDF2_MAX_ITER, PBKDF2_MIN_ITER};
9use crate::encryption::derive_setup_key;
10use crate::encryption::encrypt_session_block;
11use crate::encryption::stream::encrypt_stream;
12use crate::encryption::write::{
13 write_extensions, write_header, write_hmac, write_iterations, write_octets, write_public_iv,
14};
15use crate::error::AescryptError;
16use aes::cipher::KeyInit;
17use aes::Aes256Enc;
18use hmac::Mac;
19use secure_gate::RevealSecret;
20use std::io::{Read, Write};
21
22/// Encrypts the bytes read from `input` into a complete AES Crypt v3 file
23/// written to `output`.
24///
25/// `encrypt` consumes `input` until EOF and writes a self-contained `.aes`
26/// file: header, extensions terminator, iteration count, public IV, encrypted
27/// session block, session HMAC, ciphertext stream, and payload HMAC. The
28/// session key, session IV, and public IV are freshly generated from the
29/// [`secure-gate`] CSPRNG on every call; the same `(password, plaintext,
30/// iterations)` tuple therefore produces a different ciphertext every time.
31///
32/// # Format
33///
34/// Always v3. To produce v0/v1/v2 files, use the official `aescrypt` reference
35/// tooling — this crate intentionally does not support legacy formats on
36/// write (see the [crate-level Security Model](crate#security-model)).
37///
38/// # Errors
39///
40/// - [`AescryptError::Header`] — `password` is empty, or `kdf_iterations` is
41/// outside [`PBKDF2_MIN_ITER`](crate::constants::PBKDF2_MIN_ITER) `..=`
42/// [`PBKDF2_MAX_ITER`](crate::constants::PBKDF2_MAX_ITER).
43/// - [`AescryptError::Crypto`] — PBKDF2 setup-key derivation failed (forwarded
44/// from [`crate::derive_pbkdf2_key`]).
45/// - [`AescryptError::Io`] — `input.read` or `output.write_all` returned an
46/// error at any stage of header serialization or payload streaming.
47///
48/// # Panics
49///
50/// Never panics on valid input; the internal `expect` on `setup_key` is a
51/// 32-byte invariant that is structurally guaranteed by
52/// [`Aes256Key32`](crate::aliases::Aes256Key32).
53///
54/// # Security
55///
56/// - All secrets ([`PasswordString`], session key, session IV, setup key) live
57/// in [`secure-gate`] wrappers and zeroize on drop. Plaintext blocks
58/// transit the stack inside [`Block16`](crate::aliases::Block16) for the
59/// same reason.
60/// - The public IV doubles as the PBKDF2 salt; it is generated with
61/// `Iv16::from_random` per call and is therefore unique with overwhelming
62/// probability.
63/// - HMAC-SHA256 is computed over the encrypted session block (with the v3
64/// version byte appended) and over the ciphertext stream. Decryption verifies
65/// both with constant-time equality.
66/// - PKCS#7 padding is always applied to the final plaintext block.
67/// - `kdf_iterations` controls password-cracking cost. Use
68/// [`DEFAULT_PBKDF2_ITERATIONS`](crate::constants::DEFAULT_PBKDF2_ITERATIONS)
69/// unless you have measured your platform.
70///
71/// # Compatibility
72///
73/// - Output is byte-compatible with the official AES Crypt reference
74/// implementation for v3 files.
75/// - Files produced by this function are accepted by [`crate::decrypt()`] and
76/// by `aescrypt`'s C/.NET/Java tooling.
77///
78/// # Thread Safety
79///
80/// `encrypt` is `Send` whenever its `R`/`W` are. There is no shared mutable
81/// state, so multiple threads may call `encrypt` concurrently on independent
82/// inputs/outputs. Cancellation is the caller's responsibility — spawn in a
83/// thread and abandon the join handle, or wire up an interruptible reader/writer.
84///
85/// # Examples
86///
87/// ```no_run
88/// use aescrypt_rs::{encrypt, PasswordString, constants::DEFAULT_PBKDF2_ITERATIONS};
89/// use std::io::Cursor;
90///
91/// let password = PasswordString::new("correct horse battery staple".to_string());
92/// let plaintext = b"top secret";
93///
94/// let mut ciphertext = Vec::new();
95/// encrypt(Cursor::new(plaintext), &mut ciphertext, &password, DEFAULT_PBKDF2_ITERATIONS)?;
96/// # Ok::<(), aescrypt_rs::AescryptError>(())
97/// ```
98///
99/// Threaded usage:
100///
101/// ```no_run
102/// use aescrypt_rs::{encrypt, PasswordString};
103/// use std::io::Cursor;
104/// use std::thread;
105///
106/// let password = PasswordString::new("secret".to_string());
107/// let data = b"large file data...";
108///
109/// let handle = thread::spawn(move || {
110/// let mut encrypted = Vec::new();
111/// encrypt(Cursor::new(data), &mut encrypted, &password, 300_000)
112/// });
113///
114/// let _result = handle.join().unwrap();
115/// ```
116///
117/// # See also
118///
119/// - [`crate::decrypt()`] — inverse operation.
120/// - [`crate::Pbkdf2Builder`] — convenient way to derive a PBKDF2 key for
121/// custom flows.
122///
123/// [`secure-gate`]: https://github.com/Slurp9187/secure-gate
124#[inline(always)]
125pub fn encrypt<R, W>(
126 mut input: R,
127 mut output: W,
128 password: &PasswordString,
129 kdf_iterations: u32,
130) -> Result<(), AescryptError>
131where
132 R: Read,
133 W: Write,
134{
135 // Validation
136 if password.is_empty() {
137 return Err(AescryptError::Header("empty password".into()));
138 }
139
140 if !(PBKDF2_MIN_ITER..=PBKDF2_MAX_ITER).contains(&kdf_iterations) {
141 return Err(AescryptError::Header("invalid KDF iterations".into()));
142 }
143
144 write_header(&mut output, AESCRYPT_LATEST_VERSION)?;
145 write_extensions(&mut output, AESCRYPT_LATEST_VERSION, None)?;
146
147 // Generate secure random values — wrapped from birth
148 let public_iv = Iv16::from_random();
149 let session_iv = Iv16::from_random();
150 let session_key = Aes256Key32::from_random();
151
152 write_iterations(&mut output, kdf_iterations, AESCRYPT_LATEST_VERSION)?;
153 write_public_iv(&mut output, &public_iv)?;
154
155 // Derive setup key directly into secure buffer — zero exposure
156 let mut setup_key = Aes256Key32::new([0u8; 32]);
157 derive_setup_key(password, &public_iv, kdf_iterations, &mut setup_key)?;
158
159 // Create cipher and HMAC from secure key
160 let cipher = setup_key.with_secret(|key| Aes256Enc::new(key.into()));
161
162 let mut hmac = setup_key.with_secret(|key| {
163 <HmacSha256 as Mac>::new_from_slice(key)
164 .expect("setup_key is always 32 bytes — valid HMAC key")
165 });
166
167 // Encrypt session block
168 let mut enc_block = EncryptedSessionBlock48::new([0u8; 48]);
169 encrypt_session_block(
170 &cipher,
171 &session_iv,
172 &session_key,
173 &public_iv,
174 &mut enc_block,
175 &mut hmac,
176 )?;
177
178 // Include version byte in HMAC (v3+)
179 hmac.update(&[AESCRYPT_LATEST_VERSION]);
180
181 enc_block.with_secret(|eb| write_octets(&mut output, eb))?;
182 write_hmac(&mut output, hmac)?; // hmac moved here — correct
183
184 // Final stream encryption
185 encrypt_stream(&mut input, &mut output, &session_iv, &session_key)?;
186
187 Ok(())
188}