use crate::{transform, Md5, BLOCK_SIZE, DIGEST_LENGTH, IV, STATE_WORDS};
const IPAD: u8 = 0x36;
const OPAD: u8 = 0x5c;
pub struct HmacMd5 {
inner: Md5,
outer_state: [u32; STATE_WORDS],
}
impl HmacMd5 {
#[inline]
pub fn new(key: &[u8]) -> Self {
let mut k_block = [0u8; BLOCK_SIZE];
if key.len() > BLOCK_SIZE {
let h = crate::digest(key);
k_block[..DIGEST_LENGTH].copy_from_slice(&h);
} else {
k_block[..key.len()].copy_from_slice(key);
}
let mut ipad_block = [0u8; BLOCK_SIZE];
let mut opad_block = [0u8; BLOCK_SIZE];
for i in 0..BLOCK_SIZE {
ipad_block[i] = k_block[i] ^ IPAD;
opad_block[i] = k_block[i] ^ OPAD;
}
let mut inner_state = IV;
transform(&mut inner_state, &ipad_block);
let mut outer_state = IV;
transform(&mut outer_state, &opad_block);
Self {
inner: Md5::from_parts(inner_state, BLOCK_SIZE as u64),
outer_state,
}
}
#[inline]
pub fn update(&mut self, data: &[u8]) {
self.inner.update(data);
}
#[inline]
pub fn finalize(self) -> [u8; DIGEST_LENGTH] {
let inner_digest = self.inner.finalize();
let mut outer = Md5::from_parts(self.outer_state, BLOCK_SIZE as u64);
outer.update(&inner_digest);
outer.finalize()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(bytes: &[u8]) -> [u8; 32] {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = [0u8; 32];
for (i, b) in bytes.iter().enumerate() {
out[i * 2] = HEX[(b >> 4) as usize];
out[i * 2 + 1] = HEX[(b & 0x0f) as usize];
}
out
}
fn mac(key: &[u8], msg: &[u8]) -> [u8; DIGEST_LENGTH] {
let mut h = HmacMd5::new(key);
h.update(msg);
h.finalize()
}
#[test]
fn rfc2202_test_case_1() {
let key = [0x0bu8; 16];
let want = "9294727a3638bb1c13f48ef8158bfc9d";
let got = mac(&key, b"Hi There");
assert_eq!(core::str::from_utf8(&hex(&got)).unwrap(), want);
}
#[test]
fn rfc2202_test_case_2() {
let want = "750c783e6ab0b503eaa86e310a5db738";
let got = mac(b"Jefe", b"what do ya want for nothing?");
assert_eq!(core::str::from_utf8(&hex(&got)).unwrap(), want);
}
#[test]
fn rfc2202_test_case_3() {
let key = [0xaau8; 16];
let data = [0xddu8; 50];
let want = "56be34521d144c88dbb8c733f0e8b3f6";
let got = mac(&key, &data);
assert_eq!(core::str::from_utf8(&hex(&got)).unwrap(), want);
}
#[test]
fn rfc2202_test_case_4() {
let key: [u8; 25] = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
];
let data = [0xcdu8; 50];
let want = "697eaf0aca3a3aea3a75164746ffaa79";
let got = mac(&key, &data);
assert_eq!(core::str::from_utf8(&hex(&got)).unwrap(), want);
}
#[test]
fn rfc2202_test_case_5() {
let key = [0x0cu8; 16];
let want = "56461ef2342edc00f9bab995690efd4c";
let got = mac(&key, b"Test With Truncation");
assert_eq!(core::str::from_utf8(&hex(&got)).unwrap(), want);
}
#[test]
fn rfc2202_test_case_6_long_key() {
let key = [0xaau8; 80];
let want = "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd";
let got = mac(
&key,
b"Test Using Larger Than Block-Size Key - Hash Key First",
);
assert_eq!(core::str::from_utf8(&hex(&got)).unwrap(), want);
}
#[test]
fn rfc2202_test_case_7_long_key_and_data() {
let key = [0xaau8; 80];
let want = "6f630fad67cda0ee1fb1f562db3aa53e";
let got = mac(
&key,
b"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
);
assert_eq!(core::str::from_utf8(&hex(&got)).unwrap(), want);
}
#[test]
fn key_exactly_one_block() {
let key = [0x42u8; BLOCK_SIZE];
let direct = mac(&key, b"abc");
let mut h = HmacMd5::new(&key);
h.update(b"a");
h.update(b"b");
h.update(b"c");
let streamed = h.finalize();
assert_eq!(direct, streamed);
}
#[test]
fn streaming_matches_oneshot_multiblock() {
let key = b"radius shared secret";
let mut data = [0u8; 1000];
for (i, b) in data.iter_mut().enumerate() {
*b = (i as u8).wrapping_mul(0x9b).wrapping_add(0x37);
}
let oneshot = mac(key, &data);
let mut h = HmacMd5::new(key);
for chunk in data.chunks(13) {
h.update(chunk);
}
assert_eq!(h.finalize(), oneshot);
}
#[test]
fn empty_key_and_message() {
let want = "74e6f7298a9c2d168935f58c001bad88";
let got = mac(b"", b"");
assert_eq!(core::str::from_utf8(&hex(&got)).unwrap(), want);
}
}