one_time/
hotp.rs

1use hmac::{
2    Hmac, Mac, NewMac,
3    digest::{
4        Update, BlockInput, FixedOutput, Reset,
5        generic_array::ArrayLength,
6    },
7};
8use crate::error::Error;
9
10/// Calculate the HOTP code for the given key, counter, and digits.
11///
12/// Note that according to RFC 4226, the digest algorithm MUST be SHA1.
13/// However, this implementation will use any digest algorithm the caller
14/// sees fit to use.
15pub fn hotp<D>(key: &[u8], counter: u64, digits: u32) -> Result<u64, Error>
16where D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
17      D::BlockSize: ArrayLength<u8>,
18{
19    if digits < 6 {
20        return Err(Error::InvalidDigitsSize);
21    }
22
23    let data = counter.to_be_bytes();
24
25    let mut hmac = Hmac::<D>::new_from_slice(key).map_err(Error::InvalidKeySize)?;
26    hmac.update(&data);
27
28    let result = hmac.finalize().into_bytes();
29
30    let offset = usize::from(result[result.len() - 1] & 0xF);
31    let binary = (u64::from(result[offset] & 0x7F) << 24)
32        | (u64::from(result[offset + 1]) << 16)
33        | (u64::from(result[offset + 2]) << 8)
34        | u64::from(result[offset + 3]);
35
36    Ok(binary % 10u64.pow(digits))
37}