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
use std::cmp::Ordering;

use crate::sha1::Sha1;

pub struct HMAC;

impl HMAC {
    /// This implements HMAC for SHA1.
    /// The function takes in the bytes to hash and a secret key used to hash.
    /// The high level overview looks like this:
    /// input: &[u8]: the message to hash
    /// secret_key: &[u8]: the secret key to use in hashing.
    /// 1. The secret key is sized appropriately. (64 bytes in SHA-1)
    /// - If the key is too long or too short, it is set to 64 bytes.
    /// - If too short, it is padded with zeroes on the right
    /// - if too long, it is hashed and then padded with zeroes on the right
    /// 2. Two keys are generated
    /// - An outer key, which takes the sized key and xors it with 0x5c
    /// - And inner key, which is xored with 0x36.
    /// 3. The inner key is concatenated with the input and then hashed.
    /// 4. And then the hash is calculated of the outer key concatenated by that result.
    pub fn mac(input: &[u8], secret_key: &[u8]) -> [u8; 20] {
        // 1. If the secret key is too long, it is shortened by hashing it.
        // Otherwise, the key can be used as is.
        let block_sized_key = Self::block_size_key(secret_key);
        // 2. Next, generate two keys.
        // The first key, the outer key, is xored with 0x36.
        let mut padded = [0x36; 40];
        for (p, &k) in padded.iter_mut().zip(block_sized_key.iter()) {
            *p ^= k;
        }

        let mut ih_input = padded.to_vec();
        ih_input.extend(input);
        let ih = Sha1::hash(&ih_input);

        for p in padded.iter_mut() {
            *p ^= 0x6a;
        }
        // 3. The key is hashed with the inner key first then the outer key hashes that.
        let mut oh_input = padded.to_vec();
        oh_input.extend(&ih);
        Sha1::hash(&oh_input)
    }

    fn block_size_key(secret_key: &[u8]) -> [u8; 64] {
        match secret_key.len().cmp(&64) {
            Ordering::Less => {
                let mut res = [0; 64];
                for (i, b) in secret_key.iter().enumerate() {
                    res[i] = *b;
                }
                res
            }
            Ordering::Equal => {
                let mut res = [0; 64];
                res.copy_from_slice(secret_key);
                res
            }
            Ordering::Greater => {
                let mut res = [0; 64];
                for (i, b) in Sha1::hash(secret_key).iter().enumerate() {
                    res[i] = *b;
                }
                res
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty() {
        let h = HMAC::mac(b"", &[]);
        assert_eq!(
            h,
            [
                0x2c, 0x4c, 0x5d, 0xb0, // first
                0x09, 0x76, 0xff, 0xdb, // second
                0x10, 0xdb, 0xd5, 0x32, // third
                0xe2, 0x78, 0x35, 0xa9, // fourth
                0x84, 0x8e, 0x6c, 0xef, // fifth
            ]
        );
    }
}