1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
#![deny(missing_docs)]
//! A module that provides utility functions for computing hashes using the
//! [RustCrypto/hashes](https://github.com/RustCrypto/hashes) library.
//!
//! This module provides several functions that wrap around the hashing algorithms provided by the
//! `RustCrypto` library. These functions allow you to easily compute the hash of a file, or a
//! stream of bytes using a variety of hashing algorithms.
//!
//! By utilizing the [`Digest`] trait, any hashing algorithm that implements that trait can be used
//! with the functions provided in this crate.
//!
//! # Examples
//!
//! ```no_run
//! use rattler_digest::{compute_bytes_digest, compute_file_digest};
//! use sha2::Sha256;
//! use md5::Md5;
//!
//! // Compute the MD5 hash of a string
//! let md5_result = compute_bytes_digest::<Md5>("Hello, world!");
//! println!("MD5 hash: {:x}", md5_result);
//!
//! // Compute the SHA256 hash of a file
//! let sha256_result = compute_file_digest::<Sha256>("somefile.txt").unwrap();
//! println!("SHA256 hash: {:x}", sha256_result);
//! ```
//!
//! # Available functions
//!
//! - [`compute_file_digest`]: Computes the hash of a file on disk.
//! - [`parse_digest_from_hex`]: Given a hex representation of a digest, parses it to bytes.
//! - [`HashingWriter`]: An object that wraps a writable object and implements [`Write`] and
//! [`::tokio::io::AsyncWrite`]. It forwards the data to the wrapped object but also computes the hash of the
//! content on the fly.
//!
//! For more information on the hashing algorithms provided by the
//! [RustCrypto/hashes](https://github.com/RustCrypto/hashes) library, see the documentation for
//! that library.
#[cfg(feature = "tokio")]
mod tokio;
#[cfg(feature = "serde")]
pub mod serde;
pub use digest;
use blake2::digest::consts::U32;
use blake2::{Blake2b, Blake2bMac};
use digest::{Digest, Output};
use std::io::Read;
use std::{fs::File, io::Write, path::Path};
pub use md5::Md5;
pub use sha2::Sha256;
/// A type alias for the output of a SHA256 hash.
pub type Sha256Hash = sha2::digest::Output<Sha256>;
/// A type alias for the output of an MD5 hash.
pub type Md5Hash = md5::digest::Output<Md5>;
/// A type for a 32 bit length blake2b digest.
pub type Blake2b256 = Blake2b<U32>;
/// A type alias for the output of a [`Blake2b256`] hash.
pub type Blake2b256Hash = blake2::digest::Output<Blake2b256>;
/// A type alias for the output of a blake2b256 hash.
pub type Blake2bMac256 = Blake2bMac<U32>;
/// A type alias for the output of a [`Blake2bMac256`] hash.
pub type Blake2bMac256Hash = blake2::digest::Output<Blake2bMac256>;
/// Compute a hash of the file at the specified location.
pub fn compute_file_digest<D: Digest + Default + Write>(
path: impl AsRef<Path>,
) -> Result<Output<D>, std::io::Error> {
// Open the file for reading
let mut file = File::open(path)?;
// Determine the hash of the file on disk
let mut hasher = D::default();
std::io::copy(&mut file, &mut hasher)?;
Ok(hasher.finalize())
}
/// Compute a hash of the specified bytes.
pub fn compute_bytes_digest<D: Digest + Default + Write>(bytes: impl AsRef<[u8]>) -> Output<D> {
let mut hasher = D::default();
hasher.update(bytes);
hasher.finalize()
}
/// Parses a hash hex string to a digest.
pub fn parse_digest_from_hex<D: Digest>(str: &str) -> Option<Output<D>> {
let mut hash = <Output<D>>::default();
match hex::decode_to_slice(str, &mut hash) {
Ok(_) => Some(hash),
Err(_) => None,
}
}
/// A simple object that provides a [`Write`] implementation that also immediately hashes the bytes
/// written to it. Call [`HashingWriter::finalize`] to retrieve both the original `impl Write`
/// object as well as the hash.
///
/// If the `tokio` feature is enabled this object also implements [`::tokio::io::AsyncWrite`] which
/// allows you to use it in an async context as well.
pub struct HashingWriter<W, D: Digest> {
writer: W,
hasher: D,
}
impl<W, D: Digest + Default> HashingWriter<W, D> {
/// Constructs a new instance from a writer and a new (empty) hasher.
pub fn new(writer: W) -> Self {
Self {
writer,
hasher: Default::default(),
}
}
}
impl<W, D: Digest> HashingWriter<W, D> {
/// Consumes this instance and returns the original writer and the hash of all bytes written to
/// this instance.
pub fn finalize(self) -> (W, Output<D>) {
(self.writer, self.hasher.finalize())
}
}
impl<W: Write, D: Digest> Write for HashingWriter<W, D> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let bytes = self.writer.write(buf)?;
self.hasher.update(&buf[..bytes]);
Ok(bytes)
}
fn flush(&mut self) -> std::io::Result<()> {
self.writer.flush()
}
}
/// A simple object that provides a [`Read`] implementation that also immediately hashes the bytes
/// read from it. Call [`HashingReader::finalize`] to retrieve both the original `impl Read`
/// object as well as the hash.
///
/// If the `tokio` feature is enabled this object also implements [`::tokio::io::AsyncRead`] which
/// allows you to use it in an async context as well.
pub struct HashingReader<R, D: Digest> {
reader: R,
hasher: D,
}
impl<R, D: Digest + Default> HashingReader<R, D> {
/// Constructs a new instance from a reader and a new (empty) hasher.
pub fn new(reader: R) -> Self {
Self {
reader,
hasher: Default::default(),
}
}
}
impl<R, D: Digest> HashingReader<R, D> {
/// Consumes this instance and returns the original reader and the hash of all bytes read from
/// this instance.
pub fn finalize(self) -> (R, Output<D>) {
(self.reader, self.hasher.finalize())
}
}
impl<R: Read, D: Digest> Read for HashingReader<R, D> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let bytes_read = self.reader.read(buf)?;
self.hasher.update(&buf[..bytes_read]);
Ok(bytes_read)
}
}
#[cfg(test)]
mod test {
use super::HashingReader;
use rstest::rstest;
use sha2::Sha256;
use std::io::Read;
#[rstest]
#[case(
"1234567890",
"c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646"
)]
#[case(
"Hello, world!",
"315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"
)]
fn test_compute_file_sha256(#[case] input: &str, #[case] expected_hash: &str) {
// Write a known value to a temporary file and verify that the compute hash matches what we would
// expect.
let temp_dir = tempfile::tempdir().unwrap();
let file_path = temp_dir.path().join("test");
std::fs::write(&file_path, input).unwrap();
let hash = super::compute_file_digest::<sha2::Sha256>(&file_path).unwrap();
assert_eq!(format!("{hash:x}"), expected_hash);
}
#[rstest]
#[case(
"1234567890",
"c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646"
)]
#[case(
"Hello, world!",
"315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"
)]
fn test_hashing_reader_sha256(#[case] input: &str, #[case] expected_hash: &str) {
let mut cursor = HashingReader::<_, Sha256>::new(std::io::Cursor::new(input));
let mut cursor_string = String::new();
cursor.read_to_string(&mut cursor_string).unwrap();
assert_eq!(&cursor_string, input);
let (_, hash) = cursor.finalize();
assert_eq!(format!("{hash:x}"), expected_hash);
}
}