use crate::hmac::hmac_sha1;
pub fn hotp(secret: &[u8], counter: u64, digits: u32) -> String {
debug_assert!(digits >= 1 && digits <= 9, "digits must be in 1..=9");
let counter_bytes = counter.to_be_bytes();
let mac = hmac_sha1(secret, &counter_bytes);
let offset = (mac[19] & 0x0F) as usize;
let bin: u32 = ((mac[offset] & 0x7F) as u32) << 24
| (mac[offset + 1] as u32) << 16
| (mac[offset + 2] as u32) << 8
| (mac[offset + 3] as u32);
let modulus = 10u32.pow(digits);
let code = bin % modulus;
format!("{:0>width$}", code, width = digits as usize)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rfc4226_appendix_d() {
let secret = b"12345678901234567890";
let expected = [
"755224", "287082", "359152", "969429", "338314",
"254676", "287922", "162583", "399871", "520489",
];
for (counter, want) in expected.iter().enumerate() {
assert_eq!(
hotp(secret, counter as u64, 6),
*want,
"counter = {}",
counter
);
}
}
}