aescrypt-rs 0.2.0-rc.9

AES Crypt (v0-v3) Rust encryption/decryption library
Documentation
//! Fluent builder for PBKDF2-HMAC-SHA512 key derivation.
//!
//! See [`Pbkdf2Builder`].

use crate::aliases::{Aes256Key32, PasswordString, Salt16};
use crate::constants::DEFAULT_PBKDF2_ITERATIONS;
use crate::derive_pbkdf2_key;
use crate::error::AescryptError;

/// Fluent builder around [`crate::derive_pbkdf2_key`] with secure defaults.
///
/// `Pbkdf2Builder::new()` starts from a fresh CSPRNG-generated 16-byte salt
/// (via [`secure-gate`]'s `Salt16::from_random`) and
/// [`DEFAULT_PBKDF2_ITERATIONS`] iterations. Use [`with_salt`](Self::with_salt)
/// or [`with_iterations`](Self::with_iterations) to override either, then
/// [`derive_secure`](Self::derive_secure) to write the key into a caller
/// buffer or [`derive_secure_new`](Self::derive_secure_new) to allocate one.
///
/// # Errors
///
/// All [`Pbkdf2Builder::derive_secure*`](Self::derive_secure) methods can
/// return [`AescryptError::Crypto`] if the underlying PBKDF2 implementation
/// rejects its parameters.
///
/// # Security
///
/// - Defaults to 300 000 PBKDF2-HMAC-SHA512 iterations and a CSPRNG-backed
///   salt; these are safe for new files. Lower the iteration count only if
///   you have measured your platform.
/// - Salt and derived key live in [`secure-gate`] aliases that zeroize on
///   drop. Passwords pass through scoped `with_secret` reveals only.
/// - This builder is `Send + Sync`. It holds a salt secret but no shared
///   mutable state, so multiple threads can construct and consume their own
///   builders concurrently.
///
/// # Examples
///
/// ```
/// use aescrypt_rs::{Pbkdf2Builder, PasswordString, aliases::Aes256Key32};
///
/// let password = PasswordString::new("my-secret-password".to_string());
///
/// // Use defaults (300k iterations, random salt from `Pbkdf2Builder::new()`).
/// let mut key = Aes256Key32::new([0u8; 32]);
/// Pbkdf2Builder::new()
///     .with_salt([0x42; 16]) // Fixed salt for reproducible doctest
///     .derive_secure(&password, &mut key)?;
///
/// // Or get a new key directly.
/// let _derived_key = Pbkdf2Builder::new()
///     .with_salt([0x42; 16])
///     .derive_secure_new(&password)?;
/// # Ok::<(), aescrypt_rs::AescryptError>(())
/// ```
///
/// # See also
///
/// - [`crate::derive_pbkdf2_key`] — the underlying primitive.
/// - [`crate::encrypt()`] — full v3 encryption pipeline that uses PBKDF2
///   internally.
///
/// [`secure-gate`]: https://github.com/Slurp9187/secure-gate
#[derive(Debug)]
pub struct Pbkdf2Builder {
    iterations: u32,
    salt: Salt16,
}

impl Pbkdf2Builder {
    /// Constructs a new builder with [`DEFAULT_PBKDF2_ITERATIONS`] iterations
    /// and a freshly generated CSPRNG-backed 16-byte salt.
    ///
    /// # Security
    ///
    /// The salt is generated by [`secure-gate`]'s CSPRNG; it is unique with
    /// overwhelming probability. Keep the salt with the resulting key —
    /// PBKDF2 is not reversible without it.
    ///
    /// [`secure-gate`]: https://github.com/Slurp9187/secure-gate
    #[must_use]
    pub fn new() -> Self {
        Self {
            iterations: DEFAULT_PBKDF2_ITERATIONS,
            salt: Salt16::from_random(),
        }
    }

    /// Overrides the iteration count.
    ///
    /// `0` is silently clamped to `1` for symmetry with
    /// [`crate::derive_pbkdf2_key`]. For new files, prefer
    /// [`DEFAULT_PBKDF2_ITERATIONS`] (300 000) or higher; the encryption path
    /// rejects values outside
    /// [`PBKDF2_MIN_ITER..=PBKDF2_MAX_ITER`](crate::constants::PBKDF2_MAX_ITER).
    ///
    /// # Security
    ///
    /// Lowering iterations weakens password-cracking resistance. Do not go
    /// below [`DEFAULT_PBKDF2_ITERATIONS`] without a documented reason.
    #[must_use]
    pub fn with_iterations(mut self, iterations: u32) -> Self {
        self.iterations = iterations.max(1);
        self
    }

    /// Overrides the salt with the supplied 16-byte value.
    ///
    /// Accepts anything that converts into `[u8; 16]` (including a literal
    /// array or a [`crate::aliases::Salt16`]). The resulting salt is
    /// stored in a [`secure-gate`] alias and zeroizes on drop.
    ///
    /// # Security
    ///
    /// Salts must be unique per password to defeat rainbow-table attacks.
    /// Reusing the same salt with the same password yields the same key and
    /// undermines password hashing. Prefer [`Pbkdf2Builder::new`] for fresh
    /// random salts; only override for reproducible test vectors or when the
    /// salt comes from an existing file's public IV.
    ///
    /// [`secure-gate`]: https://github.com/Slurp9187/secure-gate
    #[must_use]
    pub fn with_salt(mut self, salt: impl Into<[u8; 16]>) -> Self {
        self.salt = Salt16::from(salt.into());
        self
    }

    /// Returns the iteration count currently configured on this builder.
    #[must_use]
    pub const fn iterations(&self) -> u32 {
        self.iterations
    }

    /// Derives the key into the caller-supplied
    /// [`crate::aliases::Aes256Key32`]. Preferred entry point.
    ///
    /// Consumes `self` so the salt and configuration cannot be reused. To
    /// derive multiple keys with the same configuration, build a new
    /// `Pbkdf2Builder` per call.
    ///
    /// # Errors
    ///
    /// - [`AescryptError::Crypto`] — PBKDF2 rejected its parameters
    ///   (forwarded from [`crate::derive_pbkdf2_key`]).
    ///
    /// # Panics
    ///
    /// Never panics on valid input.
    ///
    /// # Security
    ///
    /// `out_key` is overwritten with the derived 32 bytes; it is the caller's
    /// responsibility to keep using a [`secure-gate`]-managed buffer so the
    /// key zeroizes on drop.
    ///
    /// [`secure-gate`]: https://github.com/Slurp9187/secure-gate
    #[inline(always)]
    pub fn derive_secure(
        self,
        password: &PasswordString,
        out_key: &mut Aes256Key32,
    ) -> Result<(), AescryptError> {
        derive_pbkdf2_key(password, &self.salt, self.iterations, out_key)
    }

    /// Derives the key and returns a freshly allocated
    /// [`crate::aliases::Aes256Key32`].
    ///
    /// # Errors
    ///
    /// - [`AescryptError::Crypto`] — see
    ///   [`derive_secure`](Self::derive_secure).
    #[inline(always)]
    pub fn derive_secure_new(
        self,
        password: &PasswordString,
    ) -> Result<Aes256Key32, AescryptError> {
        let mut key = Aes256Key32::new([0u8; 32]);
        self.derive_secure(password, &mut key)?;
        Ok(key)
    }
}

impl Default for Pbkdf2Builder {
    fn default() -> Self {
        Self::new()
    }
}