Skip to main content

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}