md5_rs/
lib.rs

1#![cfg_attr(not(test), no_std)]
2mod consts;
3mod util;
4
5use consts::*;
6pub use consts::{DIGEST_LEN, INPUT_BUFFER_LEN};
7
8#[derive(Debug)]
9pub struct Context {
10    /// The total size of the recieved input
11    pub size: u64,
12    /// The input buffer
13    ///
14    /// Note: only access directly if you're writing to it, (e.g. if you want to write to it via Wasm memory)
15    pub input: [u8; INPUT_BUFFER_LEN],
16    /// The buffer for the digest
17    digest: [u8; DIGEST_LEN],
18    /// The working buffer
19    buffer: [u32; 4],
20}
21
22impl Context {
23    pub fn new() -> Self {
24        Self {
25            size: 0,
26            input: [0; INPUT_BUFFER_LEN],
27            digest: [0; DIGEST_LEN],
28            buffer: [A, B, C, D],
29        }
30    }
31
32    /// Process the bytes in `buf`
33    ///
34    /// Usage:
35    /// ```rs
36    /// let mut ctx = Context::new();
37    /// ctx.read(b"hello world");
38    /// ```
39    pub fn read(&mut self, buf: &[u8]) {
40        let mut offset = (self.size % BLOCK_SIZE as u64) as usize;
41        self.size += buf.len() as u64;
42        for i in 0..buf.len() {
43            self.input[offset] = buf[i];
44            offset += 1;
45            offset %= BLOCK_SIZE;
46            if offset == 0 {
47                self.step();
48            }
49        }
50    }
51
52    /// Process a 512-bit chunk
53    pub fn step(&mut self) {
54        let [mut a, mut b, mut c, mut d] = self.buffer;
55        let mut e: u32;
56        let mut g: usize;
57        for i in 0..BLOCK_SIZE {
58            if i < 16 {
59                e = util::f(b, c, d);
60                g = i;
61            } else if i < 32 {
62                e = util::g(b, c, d);
63                g = ((i * 5) + 1) % 16;
64            } else if i < 48 {
65                e = util::h(b, c, d);
66                g = ((i * 3) + 5) % 16;
67            } else {
68                e = util::i(b, c, d);
69                g = (i * 7) % 16;
70            }
71            g *= 4;
72
73            // get a u32 from input at index g
74            let mut u32_input: u32 = 0;
75            u32_input |= (self.input[g + 3] as u32) << 24;
76            u32_input |= (self.input[g + 2] as u32) << 16;
77            u32_input |= (self.input[g + 1] as u32) << 8;
78            u32_input |= self.input[g] as u32;
79
80            let f = a.wrapping_add(e).wrapping_add(K[i]).wrapping_add(u32_input);
81            a = d;
82            d = c;
83            c = b;
84            b = b.wrapping_add(util::rotate_u32_left(f, S[i]));
85        }
86
87        // update buffer
88        self.buffer[0] = self.buffer[0].wrapping_add(a);
89        self.buffer[1] = self.buffer[1].wrapping_add(b);
90        self.buffer[2] = self.buffer[2].wrapping_add(c);
91        self.buffer[3] = self.buffer[3].wrapping_add(d);
92    }
93
94    /// Closes the reader and returns the digest
95    ///
96    /// Usage:
97    /// ```rs
98    /// let mut ctx = Context::new();
99    /// ctx.read(b"hello world");
100    /// let digest = ctx.finish();
101    /// // prints the actual hash bytes, you need to do the hex string yourself
102    /// println!("{:?}", digest);
103    /// ```
104    pub fn finish(mut self) -> [u8; DIGEST_LEN] {
105        // Insert the padding
106        let offset = (self.size % (BLOCK_SIZE as u64)) as usize;
107        let padding_len: usize = if offset < 56 {
108            56 - offset
109        } else {
110            (56 + BLOCK_SIZE) - offset
111        };
112        self.read(&PADDING[..padding_len]);
113        self.size -= padding_len as u64;
114
115        // Do a final update
116        self.input[(INPUT_BUFFER_LEN - 8)..]
117            .copy_from_slice((self.size * 8).to_ne_bytes().as_slice());
118        self.step();
119
120        // Finalize the digest
121        for i in 0..4 {
122            self.digest[i * 4] = (self.buffer[i] & 0x000000FF) as u8;
123            self.digest[(i * 4) + 1] = ((self.buffer[i] & 0x0000FF00) >> 8) as u8;
124            self.digest[(i * 4) + 2] = ((self.buffer[i] & 0x00FF0000) >> 16) as u8;
125            self.digest[(i * 4) + 3] = ((self.buffer[i] & 0xFF000000) >> 24) as u8;
126        }
127        self.digest
128    }
129}
130
131#[cfg(test)]
132mod test {
133    use super::Context;
134
135    fn compute_string(bytes: &[u8]) -> String {
136        let mut ctx = Context::new();
137        ctx.read(bytes);
138        let digest = ctx.finish();
139        digest
140            .iter()
141            .map(|x| format!("{:02x}", x))
142            .collect::<String>()
143    }
144
145    macro_rules! hash_eq {
146        ($input:expr, $hash:expr) => {
147            assert_eq!(compute_string($input).as_str(), $hash)
148        };
149    }
150
151    #[test]
152    fn empty() {
153        hash_eq!(b"", "d41d8cd98f00b204e9800998ecf8427e")
154    }
155
156    #[test]
157    fn a() {
158        hash_eq!(b"a", "0cc175b9c0f1b6a831c399e269772661")
159    }
160
161    #[test]
162    fn abc() {
163        hash_eq!(b"abc", "900150983cd24fb0d6963f7d28e17f72")
164    }
165
166    #[test]
167    fn abcdefghijklmnopqrstuvwxyz() {
168        hash_eq!(
169            b"abcdefghijklmnopqrstuvwxyz",
170            "c3fcd3d76192e4007dfb496cca67e13b"
171        )
172    }
173
174    #[test]
175    fn foo() {
176        hash_eq!(b"foo", "acbd18db4cc2f85cedef654fccc4a4d8")
177    }
178
179    #[test]
180    fn bar() {
181        hash_eq!(b"bar", "37b51d194a7513e45b56f6524f2d51f2")
182    }
183
184    #[test]
185    fn baz() {
186        hash_eq!(b"baz", "73feffa4b7f6bb68e44cf984c85f6e88")
187    }
188
189    #[test]
190    fn foobar() {
191        hash_eq!(b"foobar", "3858f62230ac3c915f300c664312c63f")
192    }
193
194    #[test]
195    fn foobarbaz() {
196        hash_eq!(b"foobarbaz", "6df23dc03f9b54cc38a0fc1483df6e21")
197    }
198
199    #[test]
200    fn quick_brown_fox() {
201        hash_eq!(
202            b"The quick brown fox jumps over the lazy dog",
203            "9e107d9d372bb6826bd81d3542a419d6"
204        )
205    }
206
207    #[test]
208    fn hello_world() {
209        hash_eq!(b"Hello, world", "bc6e6f16b8a077ef5fbc8d59d0b931b9")
210    }
211}