mod algorithms;
pub use algorithms::{
calculate_hash_optimized, get_hardware_capabilities, BatchHasher, HardwareCapabilities,
HashAlgorithm, HashBuilder, Hasher,
};
use crate::error::{Error, Result};
use sha2::{Digest, Sha256, Sha384, Sha512};
use std::io::Read;
use std::path::Path;
use subtle::ConstantTimeEq;
pub fn calculate_hash(data: &[u8]) -> String {
calculate_hash_with_algorithm(data, &HashAlgorithm::Sha384)
}
pub fn calculate_hash_with_algorithm(data: &[u8], algorithm: &HashAlgorithm) -> String {
match algorithm {
HashAlgorithm::Sha256 => hex::encode(Sha256::digest(data)),
HashAlgorithm::Sha384 => hex::encode(Sha384::digest(data)),
HashAlgorithm::Sha512 => hex::encode(Sha512::digest(data)),
}
}
pub fn calculate_file_hash(path: impl AsRef<Path>) -> Result<String> {
calculate_file_hash_with_algorithm(path, &HashAlgorithm::Sha384)
}
pub fn calculate_file_hash_with_algorithm(
path: impl AsRef<Path>,
algorithm: &HashAlgorithm,
) -> Result<String> {
let file = std::fs::File::open(path)?;
match algorithm {
HashAlgorithm::Sha256 => hash_reader::<Sha256, _>(file),
HashAlgorithm::Sha384 => hash_reader::<Sha384, _>(file),
HashAlgorithm::Sha512 => hash_reader::<Sha512, _>(file),
}
}
pub fn combine_hashes(hashes: &[&str]) -> Result<String> {
combine_hashes_with_algorithm(hashes, &HashAlgorithm::Sha384)
}
pub fn combine_hashes_with_algorithm(hashes: &[&str], algorithm: &HashAlgorithm) -> Result<String> {
let mut combined = Vec::new();
for hash in hashes {
let bytes = hex::decode(hash)?;
combined.extend_from_slice(&bytes);
}
Ok(calculate_hash_with_algorithm(&combined, algorithm))
}
pub fn verify_hash(data: &[u8], expected_hash: &str) -> bool {
let algorithm = detect_hash_algorithm(expected_hash);
verify_hash_with_algorithm(data, expected_hash, &algorithm)
}
pub fn verify_hash_with_algorithm(
data: &[u8],
expected_hash: &str,
algorithm: &HashAlgorithm,
) -> bool {
let calculated_hash = calculate_hash_with_algorithm(data, algorithm);
constant_time_compare(&calculated_hash, expected_hash)
}
pub fn verify_file_hash(path: impl AsRef<Path>, expected_hash: &str) -> Result<bool> {
let algorithm = detect_hash_algorithm(expected_hash);
verify_file_hash_with_algorithm(path, expected_hash, &algorithm)
}
pub fn verify_file_hash_with_algorithm(
path: impl AsRef<Path>,
expected_hash: &str,
algorithm: &HashAlgorithm,
) -> Result<bool> {
let calculated_hash = calculate_file_hash_with_algorithm(path, algorithm)?;
Ok(constant_time_compare(&calculated_hash, expected_hash))
}
pub fn detect_hash_algorithm(hash: &str) -> HashAlgorithm {
match hash.len() {
64 => HashAlgorithm::Sha256,
96 => HashAlgorithm::Sha384,
128 => HashAlgorithm::Sha512,
_ => HashAlgorithm::Sha384, }
}
pub fn get_hash_length(algorithm: &HashAlgorithm) -> usize {
match algorithm {
HashAlgorithm::Sha256 => 64,
HashAlgorithm::Sha384 => 96,
HashAlgorithm::Sha512 => 128,
}
}
pub fn validate_hash_format(hash: &str) -> Result<()> {
if !hash.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(Error::Validation(
"Invalid hash: not hexadecimal".to_string(),
));
}
let valid_lengths = [64, 96, 128];
if !valid_lengths.contains(&hash.len()) {
return Err(Error::Validation(format!(
"Invalid hash length: {} (expected 64, 96, or 128)",
hash.len()
)));
}
Ok(())
}
fn hash_reader<D: Digest, R: Read>(mut reader: R) -> Result<String> {
let mut hasher = D::new();
let mut buffer = [0; 8192];
loop {
let bytes_read = reader.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
Ok(hex::encode(hasher.finalize()))
}
fn constant_time_compare(a: &str, b: &str) -> bool {
if a.len() != b.len() {
return false;
}
let a_bytes = a.as_bytes();
let b_bytes = b.as_bytes();
a_bytes.ct_eq(b_bytes).into()
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_calculate_hash() {
let data = b"test data";
let hash = calculate_hash(data);
assert_eq!(hash.len(), 96); }
#[test]
fn test_verify_hash() {
let data = b"test data";
let hash = calculate_hash(data);
assert!(verify_hash(data, &hash));
assert!(!verify_hash(b"different data", &hash));
}
#[test]
fn test_file_hash() -> Result<()> {
let dir = tempdir()?;
let file_path = dir.path().join("test.txt");
std::fs::write(&file_path, b"test content")?;
let hash = calculate_file_hash(&file_path)?;
assert_eq!(hash.len(), 96);
assert!(verify_file_hash(&file_path, &hash)?);
Ok(())
}
#[test]
fn test_combine_hashes() -> Result<()> {
let hash1 = calculate_hash(b"data1");
let hash2 = calculate_hash(b"data2");
let combined = combine_hashes(&[&hash1, &hash2])?;
assert_eq!(combined.len(), 96);
let combined_reversed = combine_hashes(&[&hash2, &hash1])?;
assert_ne!(combined, combined_reversed);
Ok(())
}
#[test]
fn test_detect_algorithm() {
let sha256 = "a".repeat(64);
let sha384 = "b".repeat(96);
let sha512 = "c".repeat(128);
assert_eq!(detect_hash_algorithm(&sha256), HashAlgorithm::Sha256);
assert_eq!(detect_hash_algorithm(&sha384), HashAlgorithm::Sha384);
assert_eq!(detect_hash_algorithm(&sha512), HashAlgorithm::Sha512);
}
}