Skip to content

Commit 6e42c38

Browse files
feat: move hash functions to dedicated hashing module and add MD5 (#1033)
1 parent d3c9028 commit 6e42c38

File tree

10 files changed

+194
-15
lines changed

10 files changed

+194
-15
lines changed

DIRECTORY.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,9 @@
4242
* [Base32](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/base32.rs)
4343
* [Base64](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/base64.rs)
4444
* [Base85](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/base85.rs)
45-
* [Blake2B](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/blake2b.rs)
4645
* [Caesar](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/caesar.rs)
4746
* [Chacha](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/chacha.rs)
4847
* [Diffie-Hellman](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/diffie_hellman.rs)
49-
* [Hashing Traits](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/hashing_traits.rs)
5048
* [Hill Cipher](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/hill_cipher.rs)
5149
* [Kernighan](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/kernighan.rs)
5250
* [Morse Code](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/morse_code.rs)
@@ -55,9 +53,6 @@
5553
* [ROT13](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/rot13.rs)
5654
* [RSA Cipher](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/rsa_cipher.rs)
5755
* [Salsa](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/salsa.rs)
58-
* [SHA-1](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/sha1.rs)
59-
* [SHA-2](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/sha2.rs)
60-
* [SHA-3](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/sha3.rs)
6156
* [Tea](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/tea.rs)
6257
* [Theoretical ROT13](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/theoretical_rot13.rs)
6358
* [Transposition](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/transposition.rs)
@@ -217,6 +212,13 @@
217212
* [Minimum Coin Change](https://github.com/TheAlgorithms/Rust/blob/master/src/greedy/minimum_coin_changes.rs)
218213
* [Smallest Range](https://github.com/TheAlgorithms/Rust/blob/master/src/greedy/smallest_range.rs)
219214
* [Stable Matching](https://github.com/TheAlgorithms/Rust/blob/master/src/greedy/stable_matching.rs)
215+
* Hashing
216+
* [Blake2B](https://github.com/TheAlgorithms/Rust/blob/master/src/hashing/blake2b.rs)
217+
* [Hashing Traits](https://github.com/TheAlgorithms/Rust/blob/master/src/hashing/hashing_traits.rs)
218+
* [MD5](https://github.com/TheAlgorithms/Rust/blob/master/src/hashing/md5.rs)
219+
* [SHA-1](https://github.com/TheAlgorithms/Rust/blob/master/src/hashing/sha1.rs)
220+
* [SHA-2](https://github.com/TheAlgorithms/Rust/blob/master/src/hashing/sha2.rs)
221+
* [SHA-3](https://github.com/TheAlgorithms/Rust/blob/master/src/hashing/sha3.rs)
220222
* [Lib](https://github.com/TheAlgorithms/Rust/blob/master/src/lib.rs)
221223
* Machine Learning
222224
* [Cholesky](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/cholesky.rs)

src/ciphers/mod.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ mod base16;
66
mod base32;
77
mod base64;
88
mod base85;
9-
mod blake2b;
109
mod caesar;
1110
mod chacha;
1211
mod diffie_hellman;
13-
mod hashing_traits;
1412
mod hill_cipher;
1513
mod kernighan;
1614
mod morse_code;
@@ -19,9 +17,6 @@ mod rail_fence;
1917
mod rot13;
2018
mod rsa_cipher;
2119
mod salsa;
22-
mod sha1;
23-
mod sha2;
24-
mod sha3;
2520
mod tea;
2621
mod theoretical_rot13;
2722
mod transposition;
@@ -38,11 +33,9 @@ pub use self::base16::{base16_decode, base16_encode};
3833
pub use self::base32::{base32_decode, base32_encode};
3934
pub use self::base64::{base64_decode, base64_encode};
4035
pub use self::base85::{base85_decode, base85_encode};
41-
pub use self::blake2b::blake2b;
4236
pub use self::caesar::caesar;
4337
pub use self::chacha::chacha20;
4438
pub use self::diffie_hellman::DiffieHellman;
45-
pub use self::hashing_traits::{Hasher, HMAC};
4639
pub use self::hill_cipher::HillCipher;
4740
pub use self::kernighan::kernighan;
4841
pub use self::morse_code::{decode, encode};
@@ -53,9 +46,6 @@ pub use self::rsa_cipher::{
5346
decrypt, decrypt_text, encrypt, encrypt_text, generate_keypair, PrivateKey, PublicKey,
5447
};
5548
pub use self::salsa::salsa20;
56-
pub use self::sha1::sha1;
57-
pub use self::sha2::{sha224, sha256, sha384, sha512, sha512_224, sha512_256};
58-
pub use self::sha3::{sha3_224, sha3_256, sha3_384, sha3_512};
5949
pub use self::tea::{tea_decrypt, tea_encrypt};
6050
pub use self::theoretical_rot13::theoretical_rot13;
6151
pub use self::transposition::transposition;

src/hashing/md5.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
use std::fmt::Write;
2+
3+
// MD5 hash function implementation
4+
// Reference: https://www.ietf.org/rfc/rfc1321.txt
5+
//
6+
// MD5 produces a 128-bit (16-byte) hash value.
7+
// Note: MD5 is cryptographically broken and should NOT be used for security
8+
// purposes. It remains useful for checksums and non-security applications.
9+
10+
/// Per-round shift amounts (RFC 1321, §3.4)
11+
const S: [u32; 64] = [
12+
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, // Round 1
13+
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, // Round 2
14+
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, // Round 3
15+
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, // Round 4
16+
];
17+
18+
/// Precomputed table of abs(sin(i+1)) * 2^32 (RFC 1321, §3.4)
19+
const K: [u32; 64] = [
20+
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
21+
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
22+
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
23+
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
24+
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
25+
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
26+
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
27+
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
28+
];
29+
30+
/// Initial hash state (RFC 1321, §3.3) — "magic" little-endian constants
31+
const INIT_STATE: [u32; 4] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476];
32+
33+
/// Computes the MD5 hash of the given byte slice.
34+
/// Returns a 16-byte array representing the 128-bit digest.
35+
pub fn md5(input: &[u8]) -> [u8; 16] {
36+
let mut state = INIT_STATE;
37+
38+
// --- Pre-processing: padding ---
39+
// Append bit '1' (0x80 byte), then zeros, then 64-bit little-endian
40+
// message length in bits, so total length ≡ 448 (mod 512) bits.
41+
let bit_len = (input.len() as u64).wrapping_mul(8);
42+
let mut msg = input.to_vec();
43+
msg.push(0x80);
44+
while msg.len() % 64 != 56 {
45+
msg.push(0x00);
46+
}
47+
msg.extend_from_slice(&bit_len.to_le_bytes());
48+
49+
// --- Processing: 512-bit (64-byte) chunks ---
50+
for chunk in msg.chunks_exact(64) {
51+
// Break chunk into 16 little-endian 32-bit words
52+
let mut m = [0u32; 16];
53+
for (i, word) in m.iter_mut().enumerate() {
54+
let offset = i * 4;
55+
*word = u32::from_le_bytes(chunk[offset..offset + 4].try_into().unwrap());
56+
}
57+
58+
let [mut a, mut b, mut c, mut d] = state;
59+
60+
for i in 0..64u32 {
61+
let (f, g) = match i {
62+
0..=15 => ((b & c) | (!b & d), i),
63+
16..=31 => ((d & b) | (!d & c), (5 * i + 1) % 16),
64+
32..=47 => (b ^ c ^ d, (3 * i + 5) % 16),
65+
_ => (c ^ (b | !d), (7 * i) % 16),
66+
};
67+
68+
let temp = d;
69+
d = c;
70+
c = b;
71+
b = b.wrapping_add(
72+
(a.wrapping_add(f)
73+
.wrapping_add(K[i as usize])
74+
.wrapping_add(m[g as usize]))
75+
.rotate_left(S[i as usize]),
76+
);
77+
a = temp;
78+
}
79+
80+
state[0] = state[0].wrapping_add(a);
81+
state[1] = state[1].wrapping_add(b);
82+
state[2] = state[2].wrapping_add(c);
83+
state[3] = state[3].wrapping_add(d);
84+
}
85+
86+
// --- Produce final digest (little-endian word order) ---
87+
let mut digest = [0u8; 16];
88+
for (i, &word) in state.iter().enumerate() {
89+
digest[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes());
90+
}
91+
digest
92+
}
93+
94+
/// Convenience helper: returns the MD5 digest as a lowercase hex string.
95+
pub fn md5_hex(input: &[u8]) -> String {
96+
md5(input)
97+
.iter()
98+
.fold(String::with_capacity(32), |mut s, b| {
99+
write!(s, "{b:02x}").unwrap();
100+
s
101+
})
102+
}
103+
104+
#[cfg(test)]
105+
mod tests {
106+
use super::*;
107+
108+
// All expected values from the RFC 1321 test suite and NIST vectors.
109+
110+
#[test]
111+
fn test_empty_string() {
112+
assert_eq!(md5_hex(b""), "d41d8cd98f00b204e9800998ecf8427e");
113+
}
114+
115+
#[test]
116+
fn test_abc() {
117+
assert_eq!(md5_hex(b"abc"), "900150983cd24fb0d6963f7d28e17f72");
118+
}
119+
120+
#[test]
121+
fn test_rfc_message() {
122+
assert_eq!(
123+
md5_hex(b"message digest"),
124+
"f96b697d7cb7938d525a2f31aaf161d0"
125+
);
126+
}
127+
128+
#[test]
129+
fn test_alphabet() {
130+
assert_eq!(
131+
md5_hex(b"abcdefghijklmnopqrstuvwxyz"),
132+
"c3fcd3d76192e4007dfb496cca67e13b"
133+
);
134+
}
135+
136+
#[test]
137+
fn test_alphanumeric() {
138+
assert_eq!(
139+
md5_hex(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"),
140+
"d174ab98d277d9f5a5611c2c9f419d9f"
141+
);
142+
}
143+
144+
#[test]
145+
fn test_digits_repeated() {
146+
assert_eq!(
147+
md5_hex(
148+
b"12345678901234567890123456789012345678901234567890123456789012345678901234567890"
149+
),
150+
"57edf4a22be3c955ac49da2e2107b67a"
151+
);
152+
}
153+
154+
#[test]
155+
fn test_single_char() {
156+
assert_eq!(md5_hex(b"a"), "0cc175b9c0f1b6a831c399e269772661");
157+
}
158+
159+
#[test]
160+
fn test_returns_16_bytes() {
161+
assert_eq!(md5(b"hello").len(), 16);
162+
}
163+
164+
#[test]
165+
fn test_deterministic() {
166+
assert_eq!(md5(b"rust"), md5(b"rust"));
167+
}
168+
169+
#[test]
170+
fn test_different_inputs_differ() {
171+
assert_ne!(md5(b"foo"), md5(b"bar"));
172+
}
173+
}

src/hashing/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
mod blake2b;
2+
mod hashing_traits;
3+
mod md5;
4+
mod sha1;
5+
mod sha2;
6+
mod sha3;
7+
8+
pub use self::blake2b::blake2b;
9+
pub use self::hashing_traits::{Hasher, HMAC};
10+
pub use self::md5::{md5, md5_hex};
11+
pub use self::sha1::sha1;
12+
pub use self::sha2::{sha224, sha256, sha384, sha512, sha512_224, sha512_256};
13+
pub use self::sha3::{sha3_224, sha3_256, sha3_384, sha3_512};
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub mod general;
1111
pub mod geometry;
1212
pub mod graph;
1313
pub mod greedy;
14+
pub mod hashing;
1415
pub mod machine_learning;
1516
pub mod math;
1617
pub mod navigation;

0 commit comments

Comments
 (0)