sanitization-crypto-interop 1.2.2

Crypto crate interop helpers for the sanitization crate.
Documentation
//! BLAKE3 helpers with explicit hasher and XOF reader cleanup.

use sanitization::SecureSanitize;
use zeroize::Zeroize;

/// Compute a 32-byte BLAKE3 digest.
///
/// The returned array is ordinary caller-owned memory. If the digest is
/// sensitive, clear it with `sanitization::sanitize_bytes` after use or move it
/// directly into a secret container.
#[must_use]
#[inline]
pub fn blake3_digest(preimage: &[u8]) -> [u8; 32] {
    let mut hasher = blake3::Hasher::new();
    hasher.update(preimage);
    let digest = *hasher.finalize().as_bytes();
    hasher.zeroize();
    digest
}

/// Compute a keyed 32-byte BLAKE3 digest.
///
/// The caller remains responsible for clearing `key` if it is stored outside a
/// `sanitization` secret container.
/// The returned array is ordinary caller-owned memory. If the digest is
/// sensitive, clear it with `sanitization::sanitize_bytes` after use or move it
/// directly into a secret container.
#[must_use]
#[inline]
pub fn blake3_keyed_digest(key: &[u8; 32], preimage: &[u8]) -> [u8; 32] {
    let mut hasher = blake3::Hasher::new_keyed(key);
    hasher.update(preimage);
    let digest = *hasher.finalize().as_bytes();
    hasher.zeroize();
    digest
}

/// Compute 64 bytes of BLAKE3 XOF output.
///
/// Both the BLAKE3 hasher and XOF reader are explicitly zeroized after the
/// output bytes are copied into the returned array.
/// The returned array is ordinary caller-owned memory. If the digest is
/// sensitive, clear it with `sanitization::sanitize_bytes` after use or move it
/// directly into a secret container.
#[must_use]
#[inline]
pub fn blake3_xof_64(preimage: &[u8]) -> [u8; 64] {
    let mut hasher = blake3::Hasher::new();
    hasher.update(preimage);
    let mut digest = [0u8; 64];
    let mut reader = hasher.finalize_xof();
    reader.fill(&mut digest);
    reader.zeroize();
    hasher.zeroize();
    digest
}

/// Compute 64 bytes of keyed BLAKE3 XOF output.
///
/// The caller remains responsible for clearing `key` if it is stored outside a
/// `sanitization` secret container.
/// The returned array is ordinary caller-owned memory. If the digest is
/// sensitive, clear it with `sanitization::sanitize_bytes` after use or move it
/// directly into a secret container.
#[must_use]
#[inline]
pub fn blake3_keyed_xof_64(key: &[u8; 32], preimage: &[u8]) -> [u8; 64] {
    let mut hasher = blake3::Hasher::new_keyed(key);
    hasher.update(preimage);
    let mut digest = [0u8; 64];
    let mut reader = hasher.finalize_xof();
    reader.fill(&mut digest);
    reader.zeroize();
    hasher.zeroize();
    digest
}

/// Fill caller-provided output with BLAKE3 XOF bytes.
///
/// This supports callers that need output lengths other than 32 or 64 bytes
/// while still clearing both the hasher and XOF reader state before returning.
#[inline]
pub fn blake3_xof_fill(preimage: &[u8], output: &mut [u8]) {
    let mut hasher = blake3::Hasher::new();
    hasher.update(preimage);
    let mut reader = hasher.finalize_xof();
    reader.fill(output);
    reader.zeroize();
    hasher.zeroize();
}

/// Fill caller-provided output with keyed BLAKE3 XOF bytes.
///
/// The caller remains responsible for clearing `key` if it is stored outside a
/// `sanitization` secret container.
#[inline]
pub fn blake3_keyed_xof_fill(key: &[u8; 32], preimage: &[u8], output: &mut [u8]) {
    let mut hasher = blake3::Hasher::new_keyed(key);
    hasher.update(preimage);
    let mut reader = hasher.finalize_xof();
    reader.fill(output);
    reader.zeroize();
    hasher.zeroize();
}

/// Clear-on-drop wrapper around [`blake3::Hasher`].
pub struct SanitizedBlake3 {
    inner: blake3::Hasher,
}

impl SanitizedBlake3 {
    /// Create a new BLAKE3 hasher.
    #[must_use]
    #[inline]
    pub fn new() -> Self {
        Self {
            inner: blake3::Hasher::new(),
        }
    }

    /// Create a new keyed BLAKE3 hasher.
    ///
    /// The caller remains responsible for clearing `key` if it is stored
    /// outside a `sanitization` secret container.
    #[must_use]
    #[inline]
    pub fn new_keyed(key: &[u8; 32]) -> Self {
        Self {
            inner: blake3::Hasher::new_keyed(key),
        }
    }

    /// Feed bytes into the hasher.
    #[inline]
    pub fn update(&mut self, input: &[u8]) {
        self.inner.update(input);
    }

    /// Finalize to a 32-byte digest, clearing the hasher state first.
    #[must_use]
    #[inline]
    pub fn finalize(mut self) -> [u8; 32] {
        let digest = *self.inner.finalize().as_bytes();
        self.inner.zeroize();
        digest
    }

    /// Finalize to 64 bytes of XOF output, clearing the reader and hasher.
    #[must_use]
    #[inline]
    pub fn finalize_xof_64(mut self) -> [u8; 64] {
        let mut digest = [0u8; 64];
        let mut reader = self.inner.finalize_xof();
        reader.fill(&mut digest);
        reader.zeroize();
        self.inner.zeroize();
        digest
    }

    /// Fill caller-provided XOF output, clearing the reader and hasher.
    #[inline]
    pub fn finalize_xof_fill(mut self, output: &mut [u8]) {
        let mut reader = self.inner.finalize_xof();
        reader.fill(output);
        reader.zeroize();
        self.inner.zeroize();
    }
}

impl Default for SanitizedBlake3 {
    #[inline]
    fn default() -> Self {
        Self::new()
    }
}

impl SecureSanitize for SanitizedBlake3 {
    #[inline]
    fn secure_sanitize(&mut self) {
        self.inner.zeroize();
        self.inner = blake3::Hasher::new();
    }
}

impl Drop for SanitizedBlake3 {
    #[inline]
    fn drop(&mut self) {
        self.inner.zeroize();
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn blake3_digest_matches_upstream() {
        assert_eq!(blake3_digest(b"hello"), *blake3::hash(b"hello").as_bytes());
    }

    #[test]
    fn blake3_xof_fill_matches_fixed_helper() {
        let mut output = [0u8; 64];
        blake3_xof_fill(b"hello", &mut output);

        assert_eq!(output, blake3_xof_64(b"hello"));
    }

    #[test]
    fn keyed_helpers_match_upstream() {
        let key = [7u8; 32];
        let mut expected = blake3::Hasher::new_keyed(&key);
        expected.update(b"hello");

        assert_eq!(
            blake3_keyed_digest(&key, b"hello"),
            *expected.finalize().as_bytes()
        );

        let mut fill = [0u8; 64];
        blake3_keyed_xof_fill(&key, b"hello", &mut fill);
        assert_eq!(fill, blake3_keyed_xof_64(&key, b"hello"));
    }

    #[test]
    fn wrapper_hashes_incrementally() {
        let mut hasher = SanitizedBlake3::new();
        hasher.update(b"hel");
        hasher.update(b"lo");

        assert_eq!(hasher.finalize(), blake3_digest(b"hello"));
    }

    #[test]
    fn wrapper_implements_secure_sanitize() {
        let mut hasher = SanitizedBlake3::new();
        hasher.update(b"secret");
        hasher.secure_sanitize();

        assert_eq!(hasher.finalize(), blake3_digest(b""));
    }
}