use crate::Digest;
const MAX_BLOCK: usize = 128;
const MAX_OUTPUT: usize = 64;
pub fn hmac<H: Digest>(key: &[u8], data: &[u8], out: &mut [u8]) {
hmac_multi::<H>(key, &[data], out);
}
pub fn hmac_multi<H: Digest>(key: &[u8], parts: &[&[u8]], out: &mut [u8]) {
let b = H::BLOCK_LEN;
let olen = H::OUTPUT_LEN;
debug_assert!(b <= MAX_BLOCK, "HMAC scratch is sized for block sizes up to 128 octets");
debug_assert!(olen <= MAX_OUTPUT, "HMAC scratch is sized for digests up to 64 octets");
debug_assert!(out.len() >= olen, "HMAC output buffer too small");
let mut k0 = [0u8; MAX_BLOCK];
if key.len() > b {
H::digest(key, &mut k0[..olen]);
} else {
k0[..key.len()].copy_from_slice(key);
}
let mut ipad = [0u8; MAX_BLOCK];
for i in 0..b {
ipad[i] = k0[i] ^ 0x36;
}
let mut inner = [0u8; MAX_OUTPUT];
let mut hi = H::new();
hi.update(&ipad[..b]);
for part in parts {
hi.update(part);
}
hi.finalize(&mut inner[..olen]);
let mut opad = [0u8; MAX_BLOCK];
for i in 0..b {
opad[i] = k0[i] ^ 0x5c;
}
let mut ho = H::new();
ho.update(&opad[..b]);
ho.update(&inner[..olen]);
ho.finalize(&mut out[..olen]);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sha2::Sha256;
fn hx(h: &str) -> alloc::vec::Vec<u8> {
(0..h.len())
.step_by(2)
.map(|i| u8::from_str_radix(&h[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn hmac_sha256_smoke() {
let key = [0x0bu8; 20];
let mut out = [0u8; 32];
hmac::<Sha256>(&key, b"Hi There", &mut out);
assert_eq!(
out[..],
hx("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7")[..]
);
}
#[test]
fn multi_part_equals_concat() {
let key = b"the-key";
let mut a = [0u8; 32];
let mut b = [0u8; 32];
hmac::<Sha256>(key, b"opt_rand-then-message", &mut a);
hmac_multi::<Sha256>(key, &[b"opt_rand-", b"then-message"], &mut b);
assert_eq!(a, b);
}
}