use std::{ fs::File, io::{BufReader, Read}, path::Path, }; use md5::{Digest as Md5Digest, Md5}; use sha1::Sha1; use sha2::{Sha256, Sha512}; use crate::error::{PakkerError, Result}; pub fn hash_to_hex(hash: impl AsRef<[u8]>) -> String { use std::fmt::Write; let bytes = hash.as_ref(); let mut hex = String::with_capacity(bytes.len() * 2); for byte in bytes { let _ = write!(hex, "{byte:02x}"); } hex } /// Compute SHA1 hash of a file pub fn compute_sha1>(path: P) -> Result { let file = File::open(path)?; let mut reader = BufReader::new(file); let mut hasher = Sha1::new(); let mut buffer = [0; 8192]; loop { let n = reader.read(&mut buffer)?; if n == 0 { break; } hasher.update(&buffer[..n]); } Ok(hash_to_hex(hasher.finalize().as_slice())) } /// Compute SHA256 hash of a file pub fn compute_sha256>(path: P) -> Result { let file = File::open(path)?; let mut reader = BufReader::new(file); let mut hasher = Sha256::new(); let mut buffer = [0; 8192]; loop { let n = reader.read(&mut buffer)?; if n == 0 { break; } hasher.update(&buffer[..n]); } Ok(hash_to_hex(hasher.finalize().as_slice())) } /// Compute SHA256 hash of byte data pub fn compute_sha256_bytes(data: &[u8]) -> String { let mut hasher = Sha256::new(); hasher.update(data); hash_to_hex(hasher.finalize().as_slice()) } /// Compute SHA512 hash of a file pub fn compute_sha512>(path: P) -> Result { let file = File::open(path)?; let mut reader = BufReader::new(file); let mut hasher = Sha512::new(); let mut buffer = [0; 8192]; loop { let n = reader.read(&mut buffer)?; if n == 0 { break; } hasher.update(&buffer[..n]); } Ok(hash_to_hex(hasher.finalize().as_slice())) } /// Compute MD5 hash of a file pub fn compute_md5>(path: P) -> Result { let file = File::open(path)?; let mut reader = BufReader::new(file); let mut hasher = Md5::new(); let mut buffer = [0; 8192]; loop { let n = reader.read(&mut buffer)?; if n == 0 { break; } hasher.update(&buffer[..n]); } let hash = hasher.finalize(); let mut hex = String::with_capacity(hash.len() * 2); for byte in hash { let _ = std::fmt::write(&mut hex, format_args!("{byte:02x}")); } Ok(hex) } /// Verify a file's hash against expected value pub fn verify_hash>( path: P, algorithm: &str, expected: &str, ) -> Result { let path = path.as_ref(); let actual = match algorithm { "sha1" => compute_sha1(path)?, "sha256" => compute_sha256(path)?, "sha512" => compute_sha512(path)?, "md5" => compute_md5(path)?, _ => { return Err(PakkerError::InternalError(format!( "Unknown hash algorithm: {algorithm}" ))); }, }; Ok(actual.eq_ignore_ascii_case(expected)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_sha256_bytes_deterministic() { let data = b"test data"; let hash1 = compute_sha256_bytes(data); let hash2 = compute_sha256_bytes(data); assert_eq!(hash1, hash2, "SHA256 must be deterministic"); } #[test] fn test_sha256_bytes_format() { let data = b"hello"; let hash = compute_sha256_bytes(data); assert_eq!(hash.len(), 64, "SHA256 hex should be 64 characters"); assert!( hash.chars().all(|c| c.is_ascii_hexdigit()), "SHA256 should only contain hex digits" ); } #[test] fn test_sha256_bytes_empty() { let hash = compute_sha256_bytes(b""); assert_eq!( hash, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ); } #[test] fn test_sha256_bytes_known_value() { // SHA256 of "hello" in hex let expected = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"; let hash = compute_sha256_bytes(b"hello"); assert_eq!(hash, expected); } }