aescrypt_rs/encryption/session.rs
1// src/encryption/session.rs
2
3//! Session-block encryption for the AES Crypt v3 format.
4//!
5//! This module mirrors [`crate::decryption::session`] on the write side. It
6//! contains the password-derived setup-key derivation and the AES-256-CBC
7//! encryption of the 48-byte session block (16-byte session IV + 32-byte
8//! session key), authenticated with HMAC-SHA256.
9//!
10//! These are pure crypto primitives — no I/O. They are exposed for advanced
11//! callers that compose their own v3-compatible encryption flow.
12
13use crate::aliases::{
14 Aes256Key32, Block16, EncryptedSessionBlock48, HmacSha256, Iv16, PasswordString,
15};
16use crate::constants::{PBKDF2_MAX_ITER, PBKDF2_MIN_ITER};
17use crate::error::AescryptError;
18use crate::kdf::pbkdf2::derive_pbkdf2_key;
19use crate::utilities::xor_blocks;
20use aes::cipher::BlockEncrypt;
21use aes::{Aes256Enc, Block as AesBlock};
22use hmac::Mac;
23use secure_gate::{RevealSecret, RevealSecretMut};
24
25/// Derives the AES-256 setup key from a password and public IV using
26/// PBKDF2-HMAC-SHA512.
27///
28/// The setup key is the master key used to encrypt the AES Crypt v3 session
29/// block. It is derived from the user's password and the per-file public IV
30/// (which doubles as the PBKDF2 salt). This is the only place in the v3
31/// encryption path where the password touches real cryptography; the bulk
32/// payload uses a separate, randomly generated session key.
33///
34/// # Errors
35///
36/// - [`AescryptError::Header`] — `iterations` is outside
37/// [`PBKDF2_MIN_ITER`](crate::constants::PBKDF2_MIN_ITER) `..=`
38/// [`PBKDF2_MAX_ITER`](crate::constants::PBKDF2_MAX_ITER).
39/// - [`AescryptError::Crypto`] — the underlying PBKDF2 implementation rejected
40/// its parameters (forwarded from [`crate::derive_pbkdf2_key`]).
41///
42/// # Security
43///
44/// - 32-byte output written directly into the caller-provided
45/// [`Aes256Key32`](crate::aliases::Aes256Key32) without ever materializing
46/// the key in a non-zeroizing buffer.
47/// - The public IV is reused as the PBKDF2 salt by the AES Crypt v3 spec; it
48/// **must** be unique per file (callers using [`crate::encrypt()`] get a
49/// CSPRNG-generated public IV automatically).
50/// - Iteration count is the only password-cracking-resistance knob; never go
51/// below [`DEFAULT_PBKDF2_ITERATIONS`](crate::constants::DEFAULT_PBKDF2_ITERATIONS)
52/// for new files.
53#[inline]
54pub fn derive_setup_key(
55 password: &PasswordString,
56 public_iv: &Iv16,
57 iterations: u32,
58 out_key: &mut Aes256Key32,
59) -> Result<(), AescryptError> {
60 if !(PBKDF2_MIN_ITER..=PBKDF2_MAX_ITER).contains(&iterations) {
61 return Err(AescryptError::Header("invalid KDF iterations".into()));
62 }
63 derive_pbkdf2_key(password, public_iv, iterations, out_key)
64}
65
66/// Encrypts the 48-byte session block (session IV + session key) under the
67/// setup key and feeds each ciphertext block into the running HMAC.
68///
69/// The session block is laid out as three 16-byte AES-CBC plaintext blocks:
70///
71/// 1. `session_iv` (16 bytes)
72/// 2. first half of `session_key` (16 bytes)
73/// 3. second half of `session_key` (16 bytes)
74///
75/// CBC chains off `public_iv`. Each ciphertext block is written to `enc_block`
76/// and folded into `hmac` (which is the same HMAC instance the caller will
77/// later finalize and serialize with [`crate::encryption::write_hmac`]).
78///
79/// # Errors
80///
81/// This function is currently infallible at the type level (returns
82/// `Ok(())`); the `Result` is preserved to keep the signature stable across
83/// future security-hardening changes.
84///
85/// # Panics
86///
87/// Never panics on valid input.
88///
89/// # Security
90///
91/// - All sensitive values (`session_iv`, `session_key`, `enc_block`) are
92/// [`secure-gate`] aliases that zeroize on drop.
93/// - `public_iv` is treated as a public, unique-per-file value (it appears in
94/// the file header verbatim).
95/// - `hmac` is keyed with the setup key by the caller; this function only
96/// updates it.
97///
98/// # Arguments
99///
100/// * `cipher` — AES-256 encryption initialized with the setup key.
101/// * `session_iv` — Randomly generated 16-byte session IV.
102/// * `session_key` — Randomly generated 32-byte session key.
103/// * `public_iv` — 16-byte public IV from the file header (CBC IV).
104/// * `enc_block` — Output buffer (48 bytes) for the encrypted session block.
105/// * `hmac` — Running HMAC-SHA256 instance, updated in place.
106///
107/// [`secure-gate`]: https://github.com/Slurp9187/secure-gate
108#[inline]
109pub fn encrypt_session_block(
110 cipher: &Aes256Enc,
111 session_iv: &Iv16,
112 session_key: &Aes256Key32,
113 public_iv: &Iv16,
114 enc_block: &mut EncryptedSessionBlock48,
115 hmac: &mut HmacSha256,
116) -> Result<(), AescryptError> {
117 let mut prev_block = public_iv.with_secret(|iv| Block16::new(*iv));
118 let mut block = Block16::new([0u8; 16]);
119
120 // === Block 1: session IV (16 bytes) ===
121 session_iv.with_secret(|siv| {
122 prev_block.with_secret(|pb| block.with_secret_mut(|b| xor_blocks(siv, pb, b)))
123 });
124 let mut aes_block = block.with_secret(|b| AesBlock::from(*b));
125 cipher.encrypt_block(&mut aes_block);
126 enc_block.with_secret_mut(|eb| eb[0..16].copy_from_slice(aes_block.as_ref()));
127 hmac.update(aes_block.as_ref());
128 let temp_block = Block16::new(*aes_block.as_ref());
129 prev_block = temp_block;
130
131 // === Block 2: first half of session key (16 bytes) ===
132 session_key.with_secret(|sk| {
133 prev_block.with_secret(|pb| block.with_secret_mut(|b| xor_blocks(&sk[0..16], pb, b)))
134 });
135 aes_block = block.with_secret(|b| AesBlock::from(*b));
136 cipher.encrypt_block(&mut aes_block);
137 enc_block.with_secret_mut(|eb| eb[16..32].copy_from_slice(aes_block.as_ref()));
138 hmac.update(aes_block.as_ref());
139 let temp_block = Block16::new(*aes_block.as_ref());
140 prev_block = temp_block;
141
142 // === Block 3: second half of session key (16 bytes) ===
143 session_key.with_secret(|sk| {
144 prev_block.with_secret(|pb| block.with_secret_mut(|b| xor_blocks(&sk[16..32], pb, b)))
145 });
146 aes_block = block.with_secret(|b| AesBlock::from(*b));
147 cipher.encrypt_block(&mut aes_block);
148 enc_block.with_secret_mut(|eb| eb[32..48].copy_from_slice(aes_block.as_ref()));
149 hmac.update(aes_block.as_ref());
150
151 Ok(())
152}