md2_digest/
lib.rs

1//! # md2_digest
2//!
3//! This crate generates the md2 digest for the bytes that are used as input.
4//!
5//! This is a `#![no_std]` crate that does not require [alloc] and has no dependencies.
6//!
7//! [alloc]: <https://doc.rust-lang.org/alloc/index.html>
8#![no_std]
9
10const PI_SUB: [u8; 256] = [
11    41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6,
12    19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188,
13    76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24,
14    138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251,
15    245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63,
16    148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50,
17    39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165,
18    181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210,
19    150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157,
20    112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27,
21    96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15,
22    85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197,
23    234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65,
24    129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123,
25    8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233,
26    203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228,
27    166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237,
28    31, 26, 219, 153, 141, 51, 159, 17, 131, 20
29];
30
31#[derive(Clone, Copy)]
32enum Padding { // Using an enum to represent the amount of padding bytes
33    Zero,      // saves space as all variants occupy the same amount
34    One,       // of space.
35    Two,
36    Three,
37    Four,
38    Five,
39    Six,
40    Seven,
41    Eight,
42    Nine,
43    Ten,
44    Eleven,
45    Twelve,
46    Thirteen,
47    Fourteen,
48    Fifteen,
49}
50
51impl Padding {
52    #[inline]
53    const fn new(input: usize) -> Self {
54        let input = 16 - if input > 0 && input < 16 {input} else {input % 16};
55        match input {
56            0 | 16 => Self::Zero, // Padding is always applied.
57            1 => Self::One,
58            2 => Self::Two,
59            3 => Self::Three,
60            4 => Self::Four,
61            5 => Self::Five,
62            6 => Self::Six,
63            7 => Self::Seven,
64            8 => Self::Eight,
65            9 => Self::Nine,
66            10 => Self::Ten,
67            11 => Self::Eleven,
68            12 => Self::Twelve,
69            13 => Self::Thirteen,
70            14 => Self::Fourteen,
71            15 => Self::Fifteen,
72            _ => unreachable!(),
73        }
74    }
75    #[inline]
76    const fn len(self) -> usize { // Using a len() method works for this implementation
77        match self {              // because the contents of all the padding bytes are
78            Self::Zero => 16,     // identical to the length of bytes they would occupy.
79            Self::One => 1,       // For example: a padding amount of 5 would be 5 bytes
80            Self::Two => 2,       // all with the value 5.
81            Self::Three => 3,     // Instead of taking up 5 bytes of space having the same
82            Self::Four => 4,      // value repeated 5 times this enum allows you to take
83            Self::Five => 5,      // up less space and still get the desired value as if
84            Self::Six => 6,       // the actual padding had occurred.
85            Self::Seven => 7,
86            Self::Eight => 8,
87            Self::Nine => 9,
88            Self::Ten => 10,
89            Self::Eleven => 11,
90            Self::Twelve => 12,
91            Self::Thirteen => 13,
92            Self::Fourteen => 14,
93            Self::Fifteen => 15,
94        }
95    }
96}
97
98#[derive(Clone, Copy)]
99struct MsgWithPadding<'msg> {
100    msg: &'msg [u8],
101    padding: Padding,
102}
103
104impl<'msg> MsgWithPadding<'msg> {
105    #[inline]
106    const fn new(msg: &'msg [u8], padding: Padding) -> Self {
107        Self {msg, padding}
108    }
109    #[inline]
110    const fn len(&self) -> usize {
111        self.msg.len() + self.padding.len()
112    }
113    #[inline]
114    #[allow(clippy::cast_possible_truncation)]
115    const fn get_inner(&self, index: usize) -> u8 {
116        if index >= self.msg.len() {self.padding.len() as u8} // cast fine, self.padding.len() never exceeds 16
117        else {self.msg[index]}
118    }
119}
120
121/// The md2 digest created from a slice of bytes.
122pub struct MD2Digest([u8; 16]);
123
124impl MD2Digest {
125    /// Generate a new [`MD2Digest`] from a slice of bytes.
126    #[must_use]
127    pub const fn new(input: &[u8]) -> Self {
128        Self (make_md2(input))
129    }
130    /// Returns the bytes of the digest in an array.
131    #[must_use]
132    pub const fn bytes(&self) -> [u8; 16] {
133        self.0
134    }
135}
136
137impl core::fmt::Display for MD2Digest {
138    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
139        write!(f, "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
140            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
141            self.0[8], self.0[9], self.0[10], self.0[11], self.0[12], self.0[13], self.0[14], self.0[15])
142    }
143}
144
145#[allow(clippy::cast_possible_truncation)]
146#[inline]
147const fn make_md2(input: &[u8]) -> [u8; 16] {
148    let msg_with_padding = MsgWithPadding::new(input, Padding::new(input.len()));
149    let checksum = checksum(msg_with_padding);
150    let msg = MsgWithChecksum::new(msg_with_padding, checksum);
151    let mut buffer = [0; 48];
152    let mut outer_index = 0;
153    while outer_index < (msg.len() / 16) {
154        let mut inner_index = 0;
155        while inner_index < 16 {
156            buffer[16 + inner_index] = msg.get_inner((outer_index * 16) + inner_index);
157            buffer[32 + inner_index] = buffer[16 + inner_index] ^ buffer[inner_index];
158            inner_index += 1;
159        }
160        let mut t = 0;
161        inner_index = 0;
162        while inner_index < 18 {
163            let mut innermost_index = 0;
164            while innermost_index < 48 {
165                t = (buffer[innermost_index]) ^ (PI_SUB[t as usize]);
166                buffer[innermost_index] = t;
167                innermost_index += 1;
168            }
169            t = t.wrapping_add(inner_index as u8); // cast fine, inner_index never exceeds 18
170            inner_index += 1;
171        }
172        outer_index += 1;
173    }
174    let mut digest = [0; 16];
175    outer_index = 0;
176    while outer_index < 16 {
177        digest[outer_index] = buffer[outer_index];
178        outer_index += 1;
179    }
180    digest
181}
182
183#[inline]
184const fn checksum(msg: MsgWithPadding) -> [u8; 16] {                 // Start with a 16 byte 0 initialised array
185    let mut checksum = [0; 16];                                      // and a 0 initialised variable to hold a value.
186    let mut l = 0;                                                   // Loop through all input bytes in groups of 16.
187    let mut outer_index = 0;                                         // Bitwise xor the input byte with the variable to
188    while outer_index < (msg.len() / 16) {                           // get an index into the PI_SUB array.
189        let mut inner_index = 0;                                     // Bitwise xor the checksum byte using the value
190        while inner_index < 16 {                                     // from the PI_SUB array.
191            let c = msg.get_inner((outer_index * 16) + inner_index); // Assign it to the variable ready for the next byte.
192            checksum[inner_index] ^= PI_SUB[(c ^ l) as usize];       // After 16 input bytes are processed the checksum
193            l = checksum[inner_index];                               // will now be filled with values.
194            inner_index += 1;                                        // Those values will then be used and overwritten
195        }                                                            // when processing the next 16 bytes.
196        outer_index += 1;                                            // Once all input bytes are processed return the
197    }                                                                // checksum ready for appending to the input.
198    checksum
199}
200
201struct MsgWithChecksum<'msg> {
202    msg_with_padding: MsgWithPadding<'msg>,
203    checksum: [u8; 16],
204}
205
206impl<'msg> MsgWithChecksum<'msg> {
207    #[inline]
208    const fn new(msg_with_padding: MsgWithPadding<'msg>, checksum: [u8; 16]) -> Self {
209        Self {msg_with_padding, checksum}
210    }
211    #[inline]
212    const fn len(&self) -> usize {
213        self.msg_with_padding.len() + self.checksum.len()
214    }
215    #[inline]
216    const fn get_inner(&self, index: usize) -> u8 {
217        if index < self.msg_with_padding.len() {self.msg_with_padding.get_inner(index)} else {
218            self.checksum[index - self.msg_with_padding.len()]
219        }
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use crate::{make_md2, checksum, MsgWithPadding};
226    const fn is_identical(first: &[u8], second: &[u8]) -> bool {
227        let mut index = 0;
228        while index != first.len() {
229            if first[index] != second[index] {return false;}
230            index += 1;
231        }
232        true
233    }
234    #[test]
235    const fn empty_md2_checksum() {
236        assert!(is_identical(
237            &checksum(MsgWithPadding::new(b"", crate::Padding::Zero)),
238            &[98, 56, 103, 182, 175, 82, 121, 94, 95, 33, 78, 151, 32, 190, 234, 141]
239        ));
240        assert!(is_identical(
241            &[98, 56, 103, 182, 175, 82, 121, 94, 95, 33, 78, 151, 32, 190, 234, 141],
242            &[0x62, 0x38, 0x67, 0xb6, 0xaf, 0x52, 0x79, 0x5e, 0x5f, 0x21, 0x4e, 0x97, 0x20, 0xbe, 0xea, 0x8d]
243        ));
244    }
245    #[test]
246    const fn empty_md2() { // see tests in rfc1319
247        assert!(is_identical(
248            &make_md2(b""),
249            &[131, 80, 229, 163, 226, 76, 21, 61, 242, 39, 92, 159, 128, 105, 39, 115]
250        ));
251        assert!(is_identical(
252            &[131, 80, 229, 163, 226, 76, 21, 61, 242, 39, 92, 159, 128, 105, 39, 115],
253            &[0x83, 0x50, 0xe5, 0xa3, 0xe2, 0x4c, 0x15, 0x3d, 0xf2, 0x27, 0x5c, 0x9f, 0x80, 0x69, 0x27, 0x73]
254        ));
255    }
256    #[test]
257    const fn a_md2_checksum() {
258        assert!(is_identical(
259            &checksum(MsgWithPadding::new(b"a", crate::Padding::Fifteen)),
260            &[25, 115, 156, 173, 163, 186, 40, 22, 147, 52, 142, 157, 37, 111, 255, 49]
261        ));
262        assert!(is_identical(
263            &[25, 115, 156, 173, 163, 186, 40, 22, 147, 52, 142, 157, 37, 111, 255, 49],
264            &[0x19, 0x73, 0x9c, 0xad, 0xa3, 0xba, 0x28, 0x16, 0x93, 0x34, 0x8e, 0x9d, 0x25, 0x6f, 0xff, 0x31]
265        ));
266    }
267    #[test]
268    const fn a_md2() { // see tests in rfc1319
269        assert!(is_identical(
270            &make_md2(b"a"),
271            &[50, 236, 1, 236, 74, 109, 172, 114, 192, 171, 150, 251, 52, 192, 181, 209]
272        ));
273        assert!(is_identical(
274            &[50, 236, 1, 236, 74, 109, 172, 114, 192, 171, 150, 251, 52, 192, 181, 209],
275            &[0x32, 0xec, 0x01, 0xec, 0x4a, 0x6d, 0xac, 0x72, 0xc0, 0xab, 0x96, 0xfb, 0x34, 0xc0, 0xb5, 0xd1]
276        ));
277    }
278    #[test]
279    const fn abc_md2_checksum() {
280        assert!(is_identical(
281            &checksum(MsgWithPadding::new(b"abc", crate::Padding::Thirteen)),
282            &[25, 226, 157, 27, 115, 4, 54, 142, 89, 90, 39, 111, 48, 47, 87, 204]
283        ));
284        assert!(is_identical(
285            &[25, 226, 157, 27, 115, 4, 54, 142, 89, 90, 39, 111, 48, 47, 87, 204],
286            &[0x19, 0xe2, 0x9d, 0x1b, 0x73, 0x04, 0x36, 0x8e, 0x59, 0x5a, 0x27, 0x6f, 0x30, 0x2f, 0x57, 0xcc]
287        ));
288    }
289    #[test]
290    const fn abc_md2() { // see tests in rfc1319
291        assert!(is_identical(
292            &make_md2(b"abc"),
293            &[218, 133, 59, 13, 63, 136, 217, 155, 48, 40, 58, 105, 230, 222, 214, 187]
294        ));
295        assert!(is_identical(
296            &[218, 133, 59, 13, 63, 136, 217, 155, 48, 40, 58, 105, 230, 222, 214, 187],
297            &[0xda, 0x85, 0x3b, 0x0d, 0x3f, 0x88, 0xd9, 0x9b, 0x30, 0x28, 0x3a, 0x69, 0xe6, 0xde, 0xd6, 0xbb]
298        ));
299    }
300    #[test]
301    const fn message_digest_md2_checksum() {
302        assert!(is_identical(
303            &checksum(MsgWithPadding::new(b"message digest", crate::Padding::Two)),
304            &[86, 214, 81, 87, 222, 223, 205, 117, 167, 177, 232, 45, 151, 14, 236, 75]
305        ));
306        assert!(is_identical(
307            &[86, 214, 81, 87, 222, 223, 205, 117, 167, 177, 232, 45, 151, 14, 236, 75],
308            &[0x56, 0xd6, 0x51, 0x57, 0xde, 0xdf, 0xcd, 0x75, 0xa7, 0xb1, 0xe8, 0x2d, 0x97, 0x0e, 0xec, 0x4b]
309        ));
310    }
311    #[test]
312    const fn message_digest_md2() { // see tests in rfc1319
313        assert!(is_identical(
314            &make_md2(b"message digest"),
315            &[171, 79, 73, 107, 251, 42, 83, 11, 33, 159, 243, 48, 49, 254, 6, 176]
316        ));
317        assert!(is_identical(
318            &[171, 79, 73, 107, 251, 42, 83, 11, 33, 159, 243, 48, 49, 254, 6, 176],
319            &[0xab, 0x4f, 0x49, 0x6b, 0xfb, 0x2a, 0x53, 0x0b, 0x21, 0x9f, 0xf3, 0x30, 0x31, 0xfe, 0x06, 0xb0]
320        ));
321    }
322    #[test]
323    const fn alphabet_md2_checksum() {
324        assert!(is_identical(
325            &checksum(MsgWithPadding::new(b"abcdefghijklmnopqrstuvwxyz", crate::Padding::Six)),
326            &[74, 66, 211, 163, 119, 183, 233, 152, 143, 185, 40, 150, 153, 228, 211, 163]
327        ));
328        assert!(is_identical(
329            &[74, 66, 211, 163, 119, 183, 233, 152, 143, 185, 40, 150, 153, 228, 211, 163],
330            &[0x4a, 0x42, 0xd3, 0xa3, 0x77, 0xb7, 0xe9, 0x98, 0x8f, 0xb9, 0x28, 0x96, 0x99, 0xe4, 0xd3, 0xa3]
331        ));
332    }
333    #[test]
334    const fn alphabet_md2() { // see tests in rfc1319
335        assert!(is_identical(
336            &make_md2(b"abcdefghijklmnopqrstuvwxyz"),
337            &[78, 141, 223, 243, 101, 2, 146, 171, 90, 65, 8, 195, 170, 71, 148, 11]
338        ));
339        assert!(is_identical(
340            &[78, 141, 223, 243, 101, 2, 146, 171, 90, 65, 8, 195, 170, 71, 148, 11],
341            &[0x4e, 0x8d, 0xdf, 0xf3, 0x65, 0x02, 0x92, 0xab, 0x5a, 0x41, 0x08, 0xc3, 0xaa, 0x47, 0x94, 0x0b]
342        ));
343    }
344    #[test]
345    const fn double_alphabet_numbers_checksum() {
346        assert!(is_identical(
347            &checksum(MsgWithPadding::new(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
348                crate::Padding::Two)),
349            &[195, 219, 117, 146, 238, 29, 217, 184, 69, 5, 207, 180, 226, 249, 167, 101]
350        ));
351        assert!(is_identical(
352            &[195, 219, 117, 146, 238, 29, 217, 184, 69, 5, 207, 180, 226, 249, 167, 101],
353            &[0xc3, 0xdb, 0x75, 0x92, 0xee, 0x1d, 0xd9, 0xb8, 0x45, 0x05, 0xcf, 0xb4, 0xe2, 0xf9, 0xa7, 0x65]
354        ));
355    }
356    #[test]
357    const fn double_alphabet_numbers() { // see tests in rfc1319
358        assert!(is_identical(
359            &make_md2(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"),
360            &[218, 51, 222, 242, 164, 45, 241, 57, 117, 53, 40, 70, 195, 3, 56, 205]
361        ));
362        assert!(is_identical(
363            &[218, 51, 222, 242, 164, 45, 241, 57, 117, 53, 40, 70, 195, 3, 56, 205],
364            &[0xda, 0x33, 0xde, 0xf2, 0xa4, 0x2d, 0xf1, 0x39, 0x75, 0x35, 0x28, 0x46, 0xc3, 0x03, 0x38, 0xcd]
365        ));
366    }
367    #[test]
368    const fn numbers_times_eight_md2_checksum() {
369        assert!(is_identical(
370            &checksum(MsgWithPadding::new(b"12345678901234567890123456789012345678901234567890123456789012345678901234567890",
371                crate::Padding::Zero)),
372            &[5, 156, 165, 103, 60, 143, 147, 27, 196, 18, 20, 245, 107, 92, 108, 1]
373        ));
374        assert!(is_identical(
375            &[5, 156, 165, 103, 60, 143, 147, 27, 196, 18, 20, 245, 107, 92, 108, 1],
376            &[0x05, 0x9c, 0xa5, 0x67, 0x3c, 0x8f, 0x93, 0x1b, 0xc4, 0x12, 0x14, 0xf5, 0x6b, 0x5c, 0x6c, 0x01]
377        ));
378    }
379    #[test]
380    const fn numbers_times_eight_md2() { // see tests in rfc1319
381        assert!(is_identical(
382            &make_md2(b"12345678901234567890123456789012345678901234567890123456789012345678901234567890"),
383            &[213, 151, 111, 121, 216, 61, 58, 13, 201, 128, 108, 60, 102, 243, 239, 216]
384        ));
385        assert!(is_identical(
386            &[213, 151, 111, 121, 216, 61, 58, 13, 201, 128, 108, 60, 102, 243, 239, 216],
387            &[0xd5, 0x97, 0x6f, 0x79, 0xd8, 0x3d, 0x3a, 0x0d, 0xc9, 0x80, 0x6c, 0x3c, 0x66, 0xf3, 0xef, 0xd8]
388        ));
389    }
390}