Skip to main content

irox_tools/hash/
mod.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5//!
6//! Message Hash (Digest) functions
7//!
8
9#![allow(clippy::indexing_slicing)]
10#![deny(clippy::integer_division_remainder_used)]
11
12use crate::cfg_feature_alloc;
13pub use blake2::*;
14use core::ops::BitXorAssign;
15use irox_bits::MutBits;
16pub use md5::MD5;
17pub use murmur3::{murmur3_128, murmur3_128_seed};
18pub use sha1::SHA1;
19pub use sha2::{SHA224, SHA256, SHA384, SHA512};
20
21mod blake2;
22pub mod md5;
23pub mod murmur3;
24pub mod sha1;
25pub mod sha2;
26pub mod sixwords;
27pub mod viz;
28
29/// Generic trait to describe a hash function
30pub trait HashDigest<const BLOCK_SIZE: usize, const OUTPUT_SIZE: usize>: Default {
31    fn write(&mut self, bytes: &[u8]);
32    fn hash(self, bytes: &[u8]) -> [u8; OUTPUT_SIZE];
33    fn finish(self) -> [u8; OUTPUT_SIZE];
34    fn algorithm() -> HashAlgorithm;
35}
36
37/// HMAC using the SHA1 algorithm
38pub type HMACSHA1 = HMAC<{ sha1::BLOCK_SIZE }, { sha1::OUTPUT_SIZE }, sha1::SHA1>;
39/// HMAC using the MD5 algorithm
40pub type HMACMD5 = HMAC<{ md5::BLOCK_SIZE }, { md5::OUTPUT_SIZE }, md5::MD5>;
41/// HMAC using the SHA224 algorithm
42pub type HMACSHA224 = HMAC<{ sha2::SHA224_BLOCK_SIZE }, { sha2::SHA224_OUTPUT_SIZE }, sha2::SHA224>;
43/// HMAC using the SHA256 algorithm
44pub type HMACSHA256 = HMAC<{ sha2::SHA256_BLOCK_SIZE }, { sha2::SHA256_OUTPUT_SIZE }, sha2::SHA256>;
45/// HMAC using the SHA384 algorithm
46pub type HMACSHA384 = HMAC<{ sha2::SHA384_BLOCK_SIZE }, { sha2::SHA384_OUTPUT_SIZE }, sha2::SHA384>;
47/// HMAC using the SHA512 algorithm
48pub type HMACSHA512 = HMAC<{ sha2::SHA512_BLOCK_SIZE }, { sha2::SHA512_OUTPUT_SIZE }, sha2::SHA512>;
49/// HMAC using the BLAKE2s algorithm
50pub type HMACBLAKE2s = HMAC<64, 32, BLAKE2s256>;
51/// HMAC using the BLAKE2b algorithm
52pub type HMACBLAKE2b = HMAC<128, 64, BLAKE2b512>;
53
54///
55/// Implementation of [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104) based on the [Wikipedia](https://en.wikipedia.org/wiki/HMAC#Implementation) algorithm
56///
57///
58/// **THIS SHOULD NOT BE USED FOR ANYTHING SECURITY RELATED**
59pub struct HMAC<
60    const BLOCK_SIZE: usize,
61    const OUTPUT_SIZE: usize,
62    T: HashDigest<BLOCK_SIZE, OUTPUT_SIZE>,
63> {
64    opad: [u8; BLOCK_SIZE],
65    alg: T,
66}
67
68impl<const BLOCK_SIZE: usize, const OUTPUT_SIZE: usize, T: HashDigest<BLOCK_SIZE, OUTPUT_SIZE>>
69    HMAC<BLOCK_SIZE, OUTPUT_SIZE, T>
70{
71    /// Creates a new HMAC, initialized with the provided key
72    pub fn new(in_key: &[u8]) -> Self {
73        let keylen = in_key.len();
74        let mut key = [0u8; BLOCK_SIZE];
75        if keylen > BLOCK_SIZE {
76            let hash = T::default().hash(in_key) as [u8; OUTPUT_SIZE];
77
78            let _ = key.as_mut_slice().write_all_bytes(&hash);
79        } else {
80            let _ = key.as_mut_slice().write_all_bytes(in_key);
81        }
82        let mut ipad = [0x36u8; BLOCK_SIZE];
83        let mut opad = [0x5Cu8; BLOCK_SIZE];
84        let mut alg = T::default();
85
86        for idx in 0..BLOCK_SIZE {
87            let k = key[idx];
88            ipad[idx].bitxor_assign(k);
89            opad[idx].bitxor_assign(k);
90        }
91        alg.write(ipad.as_slice());
92
93        Self { alg, opad }
94    }
95
96    pub fn write(&mut self, bytes: &[u8]) {
97        self.alg.write(bytes)
98    }
99    ///
100    /// Hashes the provided bytes.
101    pub fn hash(mut self, bytes: &[u8]) -> [u8; OUTPUT_SIZE] {
102        self.write(bytes);
103        self.finish()
104    }
105    ///
106    /// Finishes the hash and returns the result.
107    pub fn finish(self) -> [u8; OUTPUT_SIZE] {
108        let Self { alg, opad } = self;
109        let inner = alg.finish();
110
111        let mut outer = T::default();
112        outer.write(&opad);
113        outer.hash(&inner)
114    }
115}
116
117#[non_exhaustive]
118#[derive(Debug, Copy, Clone, Eq, PartialEq)]
119pub enum HashAlgorithm {
120    MD5,
121    SHA1,
122    SHA224,
123    SHA256,
124    SHA384,
125    SHA512,
126    Murmur3_128,
127    Murmur3_32,
128    BLAKE2s128,
129    BLAKE2s160,
130    BLAKE2s224,
131    BLAKE2s256,
132    BLAKE2b160,
133    BLAKE2b224,
134    BLAKE2b256,
135    BLAKE2b384,
136    BLAKE2b512,
137}
138impl TryFrom<&str> for HashAlgorithm {
139    type Error = ();
140
141    fn try_from(value: &str) -> Result<Self, Self::Error> {
142        Ok(match value {
143            "md5" => Self::MD5,
144            "sha1" => Self::SHA1,
145            "sha256" => Self::SHA256,
146            "sha512" => Self::SHA512,
147            "murmur3_128" | "murmur3" | "m3" => Self::Murmur3_128,
148            "b2" | "b2b" | "blake2b" | "blake2b512" => Self::BLAKE2b512,
149            "b2s" | "blake2s" | "blake2s256" => Self::BLAKE2s256,
150            _ => return Err(()),
151        })
152    }
153}
154
155cfg_feature_alloc! {
156    extern crate alloc;
157    use irox_bits::{Error, ToBEBytes};
158    use crate::hash::murmur3::{Murmur3_128, Murmur3_32};
159
160    pub struct HasherCounting {
161        pub count: u64,
162        pub hasher: Hasher,
163    }
164    impl HasherCounting {
165        pub fn write(&mut self, val: &[u8]) {
166            self.count += val.len() as u64;
167            self.hasher.write(val);
168        }
169        pub fn count(&self) -> u64 {
170            self.count
171        }
172        pub fn finish(self) -> (u64, alloc::boxed::Box<[u8]>) {
173            (self.count, self.hasher.finish())
174        }
175    }
176    impl MutBits for HasherCounting {
177        fn write_u8(&mut self, val: u8) -> Result<(), Error> {
178            self.write(&[val]);
179            Ok(())
180        }
181        fn write_all_bytes(&mut self, val: &[u8]) -> Result<(), Error> {
182            self.write(val);
183            Ok(())
184        }
185    }
186
187    impl TryFrom<HashAlgorithm> for HasherCounting {
188        type Error = Error;
189        fn try_from(value: HashAlgorithm) -> Result<Self, Self::Error> {
190            Ok(HasherCounting {
191                count: 0,
192                hasher: value.try_into()?
193            })
194        }
195    }
196
197    #[derive(Clone)]
198    pub enum Hasher {
199        MD5(MD5),
200        SHA1(SHA1),
201        SHA224(SHA224),
202        SHA256(SHA256),
203        SHA384(SHA384),
204        SHA512(SHA512),
205        Murmur3_128(Murmur3_128),
206        Murmur3_32(Murmur3_32),
207        BLAKE2b512(BLAKE2b512),
208        BLAKE2s256(BLAKE2s256),
209        BLAKE2s128(BLAKE2s128),
210        BLAKE2s160(BLAKE2s160),
211        BLAKE2s224(BLAKE2s224),
212        BLAKE2b160(BLAKE2b160),
213        BLAKE2b224(BLAKE2b224),
214        BLAKE2b256(BLAKE2b256),
215        BLAKE2b384(BLAKE2b384),
216    }
217    macro_rules! impl_hash_from {
218        ($value:ident, [$($hash:ident),+]) => {
219            match $value {
220                $(
221                    HashAlgorithm::$hash => Ok(Hasher::$hash(<$hash>::default())),
222                )*
223                _ => todo!()
224            }
225        };
226    }
227    impl TryFrom<HashAlgorithm> for Hasher {
228        type Error = Error;
229
230        fn try_from(value: HashAlgorithm) -> Result<Self, Self::Error> {
231            impl_hash_from!(value,
232                [
233                    MD5, SHA1, SHA256, SHA384, SHA512, Murmur3_128, Murmur3_32,
234                    BLAKE2s128, BLAKE2s160, BLAKE2s224, BLAKE2s256,
235                    BLAKE2b160, BLAKE2b224, BLAKE2b256, BLAKE2b384, BLAKE2b512
236                ])
237        }
238    }
239     macro_rules! impl_hash_write {
240        ($value:ident, $val:ident, [$($hash:ident),+]) => {
241            match $value {
242                $(
243                    Hasher::$hash(h) => h.write($val),
244                )*
245                _ => todo!()
246            }
247        };
248    }
249
250    macro_rules! impl_hash_finish {
251        ($value:ident, [$($hash:ident),+]) => {
252            match $value {
253                $(
254                    Hasher::$hash(v) => alloc::boxed::Box::from(v.finish().to_be_bytes()),
255                )*
256                _ => todo!()
257            }
258        };
259    }
260    impl Hasher {
261        pub fn write(&mut self, val: &[u8]) {
262            impl_hash_write!(self, val,
263                [
264                    MD5, SHA1, SHA256, SHA384, SHA512, Murmur3_128, Murmur3_32,
265                    BLAKE2s128, BLAKE2s160, BLAKE2s224, BLAKE2s256,
266                    BLAKE2b160, BLAKE2b224, BLAKE2b256, BLAKE2b384, BLAKE2b512
267                ])
268        }
269        pub fn finish(self) -> alloc::boxed::Box<[u8]> {
270            impl_hash_finish!(self,
271                [
272                    MD5, SHA1, SHA256, SHA384, SHA512, Murmur3_128, Murmur3_32,
273                    BLAKE2s128, BLAKE2s160, BLAKE2s224, BLAKE2s256,
274                    BLAKE2b160, BLAKE2b224, BLAKE2b256, BLAKE2b384, BLAKE2b512
275                ])
276        }
277        crate::cfg_feature_std! {
278            pub fn hash_file<T: AsRef<std::path::Path>>(&mut self, path: T) -> Result<(), std::io::Error> {
279                use std::io::Read;
280                let mut file = std::fs::OpenOptions::new()
281                    .read(true)
282                    .create(false)
283                    .open(path)?;
284                let mut buffer =  <alloc::boxed::Box<[u8]> as crate::buf::ZeroedBuffer>::new_zeroed(32768);
285                loop {
286                    let read = file.read(&mut buffer)?;
287                    if read == 0 {
288                        break;
289                    }
290                    if let Some(buf) = buffer.get(..read) {
291                        self.write(buf);
292                    }
293                }
294                Ok(())
295            }
296        }
297    }
298    impl MutBits for Hasher {
299        fn write_u8(&mut self, val: u8) -> Result<(), Error> {
300            self.write(&[val]);
301            Ok(())
302        }
303        fn write_all_bytes(&mut self, val: &[u8]) -> Result<(), Error> {
304            self.write(val);
305            Ok(())
306        }
307    }
308    crate::cfg_feature_std! {
309        impl HashAlgorithm {
310            pub fn hash_file<T: AsRef<std::path::Path>>(&self, path: T) -> Result<alloc::boxed::Box<[u8]>, Error> {
311                let mut hasher : Hasher = (*self).try_into()?;
312                hasher.hash_file(path)?;
313                Ok(hasher.finish())
314            }
315        }
316
317    }
318}
319#[cfg(test)]
320mod hmac_tests {
321    use crate::hash::*;
322
323    #[test]
324    pub fn wikitest1() {
325        assert_eq_hex_slice!(
326            0x80070713463e7749b90c2dc24911e275u128.to_be_bytes(),
327            HMACMD5::new("key".as_bytes())
328                .hash("The quick brown fox jumps over the lazy dog".as_bytes())
329        );
330    }
331
332    #[test]
333    pub fn wikitest2() {
334        assert_eq_hex_slice!(
335            [
336                0xde, 0x7c, 0x9b, 0x85, 0xb8, 0xb7, 0x8a, 0xa6, 0xbc, 0x8a, 0x7a, 0x36, 0xf7, 0x0a,
337                0x90, 0x70, 0x1c, 0x9d, 0xb4, 0xd9
338            ],
339            HMACSHA1::new("key".as_bytes())
340                .hash("The quick brown fox jumps over the lazy dog".as_bytes())
341        );
342    }
343
344    #[test]
345    pub fn rfctest1() {
346        assert_eq_hex_slice!(
347            0x9294727a3638bb1c13f48ef8158bfc9d_u128.to_be_bytes(),
348            HMACMD5::new(&[0x0B; 16]).hash("Hi There".as_bytes())
349        );
350    }
351    #[test]
352    pub fn rfctest2() {
353        assert_eq_hex_slice!(
354            0x750c783e6ab0b503eaa86e310a5db738_u128.to_be_bytes(),
355            HMACMD5::new("Jefe".as_bytes()).hash("what do ya want for nothing?".as_bytes())
356        );
357    }
358    #[test]
359    pub fn rfctest3() {
360        assert_eq_hex_slice!(
361            0x56be34521d144c88dbb8c733f0e8b3f6_u128.to_be_bytes(),
362            HMACMD5::new(&[0xAA; 16]).hash(&[0xDD; 50])
363        );
364    }
365}