use crate::{Result, SodiumError};
pub mod blake2b;
pub use blake2b::*;
pub const BYTES_MIN: usize = libsodium_sys::crypto_generichash_BYTES_MIN as usize;
pub const BYTES_MAX: usize = libsodium_sys::crypto_generichash_BYTES_MAX as usize;
pub const BYTES: usize = libsodium_sys::crypto_generichash_BYTES as usize;
pub const KEYBYTES_MIN: usize = libsodium_sys::crypto_generichash_KEYBYTES_MIN as usize;
pub const KEYBYTES_MAX: usize = libsodium_sys::crypto_generichash_KEYBYTES_MAX as usize;
pub const KEYBYTES: usize = libsodium_sys::crypto_generichash_KEYBYTES as usize;
pub struct State {
state: libsodium_sys::crypto_generichash_state,
output_len: usize,
}
impl Drop for State {
fn drop(&mut self) {
unsafe {
libsodium_sys::sodium_memzero(
&mut self.state as *mut _ as *mut libc::c_void,
std::mem::size_of::<libsodium_sys::crypto_generichash_state>(),
);
}
}
}
impl State {
pub fn new(key: Option<&[u8]>, output_len: usize) -> Result<Self> {
if !(BYTES_MIN..=BYTES_MAX).contains(&output_len) {
return Err(SodiumError::InvalidInput(format!(
"Output length must be between {BYTES_MIN} and {BYTES_MAX} bytes"
)));
}
if let Some(key) = key {
if key.len() < KEYBYTES_MIN || key.len() > KEYBYTES_MAX {
return Err(SodiumError::InvalidInput(format!(
"Key length must be between {KEYBYTES_MIN} and {KEYBYTES_MAX} bytes"
)));
}
}
let mut state = Self {
state: unsafe { std::mem::zeroed() },
output_len,
};
let result = match key {
Some(key) => unsafe {
libsodium_sys::crypto_generichash_init(
&mut state.state,
key.as_ptr(),
key.len() as libc::size_t,
output_len as libc::size_t,
)
},
None => unsafe {
libsodium_sys::crypto_generichash_init(
&mut state.state,
std::ptr::null(),
0,
output_len as libc::size_t,
)
},
};
if result != 0 {
return Err(SodiumError::OperationError(
"Failed to initialize BLAKE2b state".to_string(),
));
}
Ok(state)
}
pub fn update(&mut self, input: &[u8]) {
unsafe {
libsodium_sys::crypto_generichash_update(
&mut self.state,
input.as_ptr(),
input.len() as u64,
);
}
}
pub fn finalize(&mut self) -> Vec<u8> {
let mut out = vec![0u8; self.output_len];
unsafe {
libsodium_sys::crypto_generichash_final(
&mut self.state,
out.as_mut_ptr(),
out.len() as libc::size_t,
);
}
out
}
}
pub fn generichash(input: &[u8], key: Option<&[u8]>, output_len: usize) -> Result<Vec<u8>> {
if !(BYTES_MIN..=BYTES_MAX).contains(&output_len) {
return Err(SodiumError::InvalidInput(format!(
"Output length must be between {BYTES_MIN} and {BYTES_MAX}"
)));
}
if let Some(key) = key {
if key.len() < KEYBYTES_MIN || key.len() > KEYBYTES_MAX {
return Err(SodiumError::InvalidKey(format!(
"Key length must be between {KEYBYTES_MIN} and {KEYBYTES_MAX}"
)));
}
}
let mut state = State::new(key, output_len)?;
state.update(input);
Ok(state.finalize())
}
#[cfg(test)]
mod tests {
use super::*;
use ct_codecs::{Encoder, Hex};
#[test]
fn test_generichash() {
let data = b"test data";
let hash = generichash(data, None, BYTES).unwrap();
assert_eq!(
{
let mut encoded = vec![0u8; hash.len() * 2]; let encoded = Hex::encode(&mut encoded, &hash).unwrap();
std::str::from_utf8(encoded).unwrap().to_string()
},
"eab94977a17791d0c089fe9e393261b3ab667cf0e8456632a842d905c468cf65"
);
}
#[test]
fn test_generichash_with_key() {
let data = b"test data";
let key = vec![0u8; KEYBYTES]; let hash = generichash(data, Some(&key), BYTES).unwrap();
assert_eq!(
{
let mut encoded = vec![0u8; hash.len() * 2]; let encoded = Hex::encode(&mut encoded, &hash).unwrap();
std::str::from_utf8(encoded).unwrap().to_string()
},
"9e34d14a3d2082187f56b14df4e9aaf36b0562e0f842b5b323555192b0c08c22"
);
}
#[test]
fn test_generichash_incremental() {
let mut state = State::new(None, BYTES).expect("Failed to create BLAKE2b state");
state.update(b"test ");
state.update(b"data");
let hash = state.finalize();
let mut encoded = vec![0u8; hash.len() * 2]; let encoded = Hex::encode(&mut encoded, &hash).unwrap();
assert_eq!(
std::str::from_utf8(encoded).unwrap(),
"eab94977a17791d0c089fe9e393261b3ab667cf0e8456632a842d905c468cf65"
);
}
#[test]
fn test_invalid_output_length() {
assert!(generichash(b"test", None, BYTES_MAX + 1).is_err());
assert!(generichash(b"test", None, BYTES_MIN - 1).is_err());
}
#[test]
fn test_invalid_key_length() {
let long_key = vec![0u8; KEYBYTES_MAX + 1];
assert!(generichash(b"test", Some(&long_key), BYTES).is_err());
}
}