use crate::Digest;
use crate::error::Result;
const BLOCK64: usize = 64;
const BLOCK128: usize = 128;
const IPAD: u8 = 0x36;
const OPAD: u8 = 0x5c;
fn pad64<const D: usize>(
key: &[u8],
hash: impl FnOnce(&[u8]) -> Result<[u8; D]>,
) -> Result<([u8; 64], [u8; 64])> {
let mut k = [0u8; BLOCK64];
if key.len() > BLOCK64 {
k[..D].copy_from_slice(&hash(key)?);
} else {
k[..key.len()].copy_from_slice(key);
}
let mut ipad = [IPAD; BLOCK64];
let mut opad = [OPAD; BLOCK64];
for (i, &byte) in k.iter().enumerate() {
ipad[i] ^= byte;
opad[i] ^= byte;
}
Ok((ipad, opad))
}
fn pad128(key: &[u8]) -> ([u8; BLOCK128], [u8; BLOCK128]) {
let mut k = [0u8; BLOCK128];
if key.len() > BLOCK128 {
k[..64].copy_from_slice(crate::sha512(key).as_bytes());
} else {
k[..key.len()].copy_from_slice(key);
}
let mut ipad = [IPAD; BLOCK128];
let mut opad = [OPAD; BLOCK128];
for (i, &byte) in k.iter().enumerate() {
ipad[i] ^= byte;
opad[i] ^= byte;
}
(ipad, opad)
}
pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<Digest<32>> {
let (ipad, opad) = pad64(key, |k| Ok(*crate::sha256(k)?.as_bytes()))?;
let mut h = crate::sha256::H0;
let _ = crate::sha256::absorb(&mut h, &ipad);
let rem = crate::sha256::absorb(&mut h, data);
let inner = crate::sha256::finish(&h, rem, (BLOCK64 + data.len()) as u64);
let mut inner_bytes = [0u8; 32];
for (word, chunk) in inner.iter().zip(inner_bytes.chunks_exact_mut(4)) {
chunk.copy_from_slice(&word.to_be_bytes());
}
let mut h = crate::sha256::H0;
let _ = crate::sha256::absorb(&mut h, &opad);
let words = crate::sha256::finish(&h, &inner_bytes, (BLOCK64 + 32) as u64);
let mut out = [0u8; 32];
for (word, chunk) in words.iter().zip(out.chunks_exact_mut(4)) {
chunk.copy_from_slice(&word.to_be_bytes());
}
Ok(Digest::from_bytes(out))
}
pub fn hmac_sha1(key: &[u8], data: &[u8]) -> Result<Digest<20>> {
let (ipad, opad) = pad64(key, |k| {
let mut out = [0u8; 20];
out.copy_from_slice(crate::sha1(k)?.as_bytes());
Ok(out)
})?;
let mut h = crate::sha1::H0;
let _ = crate::sha1::absorb(&mut h, &ipad);
let rem = crate::sha1::absorb(&mut h, data);
let inner = crate::sha1::finish(&h, rem, (BLOCK64 + data.len()) as u64);
let mut inner_bytes = [0u8; 20];
for (word, chunk) in inner.iter().zip(inner_bytes.chunks_exact_mut(4)) {
chunk.copy_from_slice(&word.to_be_bytes());
}
let mut h = crate::sha1::H0;
let _ = crate::sha1::absorb(&mut h, &opad);
let words = crate::sha1::finish(&h, &inner_bytes, (BLOCK64 + 20) as u64);
let mut out = [0u8; 20];
for (word, chunk) in words.iter().zip(out.chunks_exact_mut(4)) {
chunk.copy_from_slice(&word.to_be_bytes());
}
Ok(Digest::from_bytes(out))
}
#[must_use]
pub fn hmac_sha512(key: &[u8], data: &[u8]) -> Digest<64> {
let (ipad, opad) = pad128(key);
let mut h = crate::sha512::H0;
let _ = crate::sha512::absorb(&mut h, &ipad);
let rem = crate::sha512::absorb(&mut h, data);
let inner = crate::sha512::finish512(&h, rem, (BLOCK128 + data.len()) as u64);
let mut inner_bytes = [0u8; 64];
for (word, chunk) in inner.iter().zip(inner_bytes.chunks_exact_mut(8)) {
chunk.copy_from_slice(&word.to_be_bytes());
}
let mut h = crate::sha512::H0;
let _ = crate::sha512::absorb(&mut h, &opad);
let words = crate::sha512::finish512(&h, &inner_bytes, (BLOCK128 + 64) as u64);
let mut out = [0u8; 64];
for (word, chunk) in words.iter().zip(out.chunks_exact_mut(8)) {
chunk.copy_from_slice(&word.to_be_bytes());
}
Digest::from_bytes(out)
}
#[cfg(test)]
mod tests {
use super::{hmac_sha1, hmac_sha256, hmac_sha512};
#[test]
fn rfc_2202_hmac_sha1_vectors() {
let key = [0x0b_u8; 20];
assert_eq!(
hmac_sha1(&key, b"Hi There").unwrap().to_string(),
"b617318655057264e28bc0b6fb378c8ef146be00"
);
assert_eq!(
hmac_sha1(b"Jefe", b"what do ya want for nothing?")
.unwrap()
.to_string(),
"effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"
);
let long_key = [0xaa_u8; 80];
assert_eq!(
hmac_sha1(
&long_key,
b"Test Using Larger Than Block-Size Key - Hash Key First"
)
.unwrap()
.to_string(),
"aa4ae5e15272d00e95705637ce8a3b55ed402112"
);
}
#[test]
fn rfc_4231_hmac_sha256_vectors() {
let key = [0x0b_u8; 20];
assert_eq!(
hmac_sha256(&key, b"Hi There").unwrap().to_string(),
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
);
assert_eq!(
hmac_sha256(b"Jefe", b"what do ya want for nothing?")
.unwrap()
.to_string(),
"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
);
let long_key = [0xaa_u8; 131];
assert_eq!(
hmac_sha256(
&long_key,
b"This is a test using a larger than block-size key and a \
larger than block-size data. The key needs to be hashed \
before being used by the HMAC algorithm.",
)
.unwrap()
.to_string(),
"9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"
);
}
#[test]
fn rfc_4231_hmac_sha512_vectors() {
let key = [0x0b_u8; 20];
assert_eq!(
hmac_sha512(&key, b"Hi There").to_string(),
concat!(
"87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cd",
"edaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
)
);
assert_eq!(
hmac_sha512(b"Jefe", b"what do ya want for nothing?").to_string(),
concat!(
"164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea250554",
"9758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737",
)
);
let long_key = [0xaa_u8; 131];
assert_eq!(
hmac_sha512(
&long_key,
b"This is a test using a larger than block-size key and a \
larger than block-size data. The key needs to be hashed \
before being used by the HMAC algorithm.",
)
.to_string(),
concat!(
"e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944",
"b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58",
)
);
}
#[test]
fn empty_key_and_message() {
assert_eq!(
hmac_sha1(b"", b"").unwrap().to_string(),
"fbdb1d1b18aa6c08324b7d64b71fb76370690e1d"
);
assert_eq!(
hmac_sha256(b"", b"").unwrap().to_string(),
"b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
);
assert_eq!(
hmac_sha512(b"", b"").to_string(),
concat!(
"b936cee86c9f87aa5d3c6f2e84cb5a4239a5fe50480a6ec66b70ab5b1f4ac673",
"0c6c515421b327ec1d69402e53dfb49ad7381eb067b338fd7b0cb22247225d47",
)
);
}
#[test]
fn key_block_boundaries() {
assert_eq!(
hmac_sha256(&[0x5a_u8; 64], b"pith").unwrap().to_string(),
"ce01adb54159b9c7c722ea928b1dca33b866e4147e924a7e0da34d08bf04731e"
);
assert_eq!(
hmac_sha256(&[0x5a_u8; 65], b"pith").unwrap().to_string(),
"d46d5cc612e1c3570701177e2850acb85add57dbede0dcfcbb472e0e83f5736d"
);
assert_eq!(
hmac_sha512(&[0x5a_u8; 128], b"pith").to_string(),
concat!(
"4bc48fec9e29b700f99a4909d60a02728d59b5f198f31ef0b6463c672504f1bd",
"55758af5b6523d62dd734996c0fa9fdd6d1dca163a5c066de9816f311892ecf1",
)
);
assert_eq!(
hmac_sha512(&[0x5a_u8; 129], b"pith").to_string(),
concat!(
"fb7360804ba4788fdbef6656dabc7d8c96612ee8861fd6165d890cff3da864e8",
"45b31d639f9bde9e08dd7755d0b8834f1a8de372d18190146afc40f7d394222a",
)
);
assert_eq!(
hmac_sha1(&[0x5a_u8; 64], b"pith").unwrap().to_string(),
"14fd693305d0a13e6fa3940e8b6296323837fe4d"
);
assert_eq!(
hmac_sha1(&[0x5a_u8; 65], b"pith").unwrap().to_string(),
"03ca862849e9348f89ef6fac14dd68f43c7ea391"
);
}
}