Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I411be69ff31f9cb39cd4cdebc8985b366a6a6964
169 lines
3.9 KiB
Rust
169 lines
3.9 KiB
Rust
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<P: AsRef<Path>>(path: P) -> Result<String> {
|
|
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<P: AsRef<Path>>(path: P) -> Result<String> {
|
|
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<P: AsRef<Path>>(path: P) -> Result<String> {
|
|
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<P: AsRef<Path>>(path: P) -> Result<String> {
|
|
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<P: AsRef<Path>>(
|
|
path: P,
|
|
algorithm: &str,
|
|
expected: &str,
|
|
) -> Result<bool> {
|
|
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);
|
|
}
|
|
}
|