1mod algorithms;
30
31pub use algorithms::{
32 calculate_hash_optimized, get_hardware_capabilities, BatchHasher, HardwareCapabilities,
33 HashAlgorithm, HashBuilder, Hasher,
34};
35
36use crate::error::{Error, Result};
37use sha2::{Digest, Sha256, Sha384, Sha512};
38use std::io::Read;
39use std::path::Path;
40use subtle::ConstantTimeEq;
41
42pub fn calculate_hash(data: &[u8]) -> String {
44 calculate_hash_with_algorithm(data, &HashAlgorithm::Sha384)
45}
46
47pub fn calculate_hash_with_algorithm(data: &[u8], algorithm: &HashAlgorithm) -> String {
49 match algorithm {
50 HashAlgorithm::Sha256 => hex::encode(Sha256::digest(data)),
51 HashAlgorithm::Sha384 => hex::encode(Sha384::digest(data)),
52 HashAlgorithm::Sha512 => hex::encode(Sha512::digest(data)),
53 }
54}
55
56pub fn calculate_file_hash(path: impl AsRef<Path>) -> Result<String> {
68 calculate_file_hash_with_algorithm(path, &HashAlgorithm::Sha384)
69}
70
71pub fn calculate_file_hash_with_algorithm(
83 path: impl AsRef<Path>,
84 algorithm: &HashAlgorithm,
85) -> Result<String> {
86 let file = std::fs::File::open(path)?;
87
88 match algorithm {
89 HashAlgorithm::Sha256 => hash_reader::<Sha256, _>(file),
90 HashAlgorithm::Sha384 => hash_reader::<Sha384, _>(file),
91 HashAlgorithm::Sha512 => hash_reader::<Sha512, _>(file),
92 }
93}
94
95pub fn combine_hashes(hashes: &[&str]) -> Result<String> {
114 combine_hashes_with_algorithm(hashes, &HashAlgorithm::Sha384)
115}
116
117pub fn combine_hashes_with_algorithm(hashes: &[&str], algorithm: &HashAlgorithm) -> Result<String> {
123 let mut combined = Vec::new();
124 for hash in hashes {
125 let bytes = hex::decode(hash)?;
126 combined.extend_from_slice(&bytes);
127 }
128 Ok(calculate_hash_with_algorithm(&combined, algorithm))
129}
130
131pub fn verify_hash(data: &[u8], expected_hash: &str) -> bool {
146 let algorithm = detect_hash_algorithm(expected_hash);
147 verify_hash_with_algorithm(data, expected_hash, &algorithm)
148}
149
150pub fn verify_hash_with_algorithm(
154 data: &[u8],
155 expected_hash: &str,
156 algorithm: &HashAlgorithm,
157) -> bool {
158 let calculated_hash = calculate_hash_with_algorithm(data, algorithm);
159 constant_time_compare(&calculated_hash, expected_hash)
160}
161
162pub fn verify_file_hash(path: impl AsRef<Path>, expected_hash: &str) -> Result<bool> {
168 let algorithm = detect_hash_algorithm(expected_hash);
169 verify_file_hash_with_algorithm(path, expected_hash, &algorithm)
170}
171
172pub fn verify_file_hash_with_algorithm(
178 path: impl AsRef<Path>,
179 expected_hash: &str,
180 algorithm: &HashAlgorithm,
181) -> Result<bool> {
182 let calculated_hash = calculate_file_hash_with_algorithm(path, algorithm)?;
183 Ok(constant_time_compare(&calculated_hash, expected_hash))
184}
185
186pub fn detect_hash_algorithm(hash: &str) -> HashAlgorithm {
202 match hash.len() {
203 64 => HashAlgorithm::Sha256,
204 96 => HashAlgorithm::Sha384,
205 128 => HashAlgorithm::Sha512,
206 _ => HashAlgorithm::Sha384, }
208}
209
210pub fn get_hash_length(algorithm: &HashAlgorithm) -> usize {
222 match algorithm {
223 HashAlgorithm::Sha256 => 64,
224 HashAlgorithm::Sha384 => 96,
225 HashAlgorithm::Sha512 => 128,
226 }
227}
228
229pub fn validate_hash_format(hash: &str) -> Result<()> {
248 if !hash.chars().all(|c| c.is_ascii_hexdigit()) {
249 return Err(Error::Validation(
250 "Invalid hash: not hexadecimal".to_string(),
251 ));
252 }
253
254 let valid_lengths = [64, 96, 128];
255 if !valid_lengths.contains(&hash.len()) {
256 return Err(Error::Validation(format!(
257 "Invalid hash length: {} (expected 64, 96, or 128)",
258 hash.len()
259 )));
260 }
261
262 Ok(())
263}
264
265fn hash_reader<D: Digest, R: Read>(mut reader: R) -> Result<String> {
267 let mut hasher = D::new();
268 let mut buffer = [0; 8192];
269
270 loop {
271 let bytes_read = reader.read(&mut buffer)?;
272 if bytes_read == 0 {
273 break;
274 }
275 hasher.update(&buffer[..bytes_read]);
276 }
277
278 Ok(hex::encode(hasher.finalize()))
279}
280
281fn constant_time_compare(a: &str, b: &str) -> bool {
283 if a.len() != b.len() {
284 return false;
285 }
286
287 let a_bytes = a.as_bytes();
288 let b_bytes = b.as_bytes();
289
290 a_bytes.ct_eq(b_bytes).into()
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use tempfile::tempdir;
297
298 #[test]
299 fn test_calculate_hash() {
300 let data = b"test data";
301 let hash = calculate_hash(data);
302 assert_eq!(hash.len(), 96); }
304
305 #[test]
306 fn test_verify_hash() {
307 let data = b"test data";
308 let hash = calculate_hash(data);
309 assert!(verify_hash(data, &hash));
310 assert!(!verify_hash(b"different data", &hash));
311 }
312
313 #[test]
314 fn test_file_hash() -> Result<()> {
315 let dir = tempdir()?;
316 let file_path = dir.path().join("test.txt");
317 std::fs::write(&file_path, b"test content")?;
318
319 let hash = calculate_file_hash(&file_path)?;
320 assert_eq!(hash.len(), 96);
321
322 assert!(verify_file_hash(&file_path, &hash)?);
323
324 Ok(())
325 }
326
327 #[test]
328 fn test_combine_hashes() -> Result<()> {
329 let hash1 = calculate_hash(b"data1");
330 let hash2 = calculate_hash(b"data2");
331
332 let combined = combine_hashes(&[&hash1, &hash2])?;
333 assert_eq!(combined.len(), 96);
334
335 let combined_reversed = combine_hashes(&[&hash2, &hash1])?;
337 assert_ne!(combined, combined_reversed);
338
339 Ok(())
340 }
341
342 #[test]
343 fn test_detect_algorithm() {
344 let sha256 = "a".repeat(64);
345 let sha384 = "b".repeat(96);
346 let sha512 = "c".repeat(128);
347
348 assert_eq!(detect_hash_algorithm(&sha256), HashAlgorithm::Sha256);
349 assert_eq!(detect_hash_algorithm(&sha384), HashAlgorithm::Sha384);
350 assert_eq!(detect_hash_algorithm(&sha512), HashAlgorithm::Sha512);
351 }
352}