#![doc(html_root_url = "https://docs.rs/totp-lite/1.1.0")]
use digest::{
block_buffer::Eager,
core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore},
generic_array::typenum::{IsLess, Le, NonZero, U256},
FixedOutput, HashMarker, Update,
};
use hmac::{Hmac, Mac};
pub use sha1::Sha1;
pub use sha2::{Sha256, Sha512};
pub const DEFAULT_STEP: u64 = 30;
pub const DEFAULT_DIGITS: u32 = 8;
pub fn totp<H>(secret: &[u8], time: u64) -> String
where
H: Update + FixedOutput + CoreProxy,
H::Core: HashMarker
+ UpdateCore
+ FixedOutputCore
+ BufferKindUser<BufferKind = Eager>
+ Default
+ Clone,
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
totp_custom::<H>(DEFAULT_STEP, DEFAULT_DIGITS, secret, time)
}
pub fn totp_custom<H>(step: u64, digits: u32, secret: &[u8], time: u64) -> String
where
H: Update + FixedOutput + CoreProxy,
H::Core: HashMarker
+ UpdateCore
+ FixedOutputCore
+ BufferKindUser<BufferKind = Eager>
+ Default
+ Clone,
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
let mut mac = <Hmac<H> as Mac>::new_from_slice(secret).unwrap();
<Hmac<H> as Update>::update(&mut mac, &to_bytes(time / step));
let hash: &[u8] = &mac.finalize().into_bytes();
let offset: usize = (hash.last().unwrap() & 0xf) as usize;
let binary: u64 = (((hash[offset] & 0x7f) as u64) << 24)
| ((hash[offset + 1] as u64) << 16)
| ((hash[offset + 2] as u64) << 8)
| (hash[offset + 3] as u64);
format!("{:01$}", binary % (10_u64.pow(digits)), digits as usize)
}
fn to_bytes(n: u64) -> [u8; 8] {
let mask = 0x00000000000000ff;
let mut bytes: [u8; 8] = [0; 8];
(0..8).for_each(|i| bytes[7 - i] = (mask & (n >> (i * 8))) as u8);
bytes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_bytes_test() {
assert_eq!(vec![0, 0, 0, 0, 0, 0, 0, 1], to_bytes(59 / DEFAULT_STEP));
assert_eq!(
vec![0, 0, 0, 0, 0x02, 0x35, 0x23, 0xec],
to_bytes(1111111109 / DEFAULT_STEP)
);
assert_eq!(
vec![0, 0, 0, 0, 0x27, 0xbc, 0x86, 0xaa],
to_bytes(20000000000 / DEFAULT_STEP)
);
}
#[test]
fn variable_length() {
let secret: &[u8] = b"12345678901234567890123456789012";
assert_eq!(
"2102975832",
totp_custom::<Sha256>(DEFAULT_STEP, 10, secret, 100)
);
assert_eq!(
"975832",
totp_custom::<Sha256>(DEFAULT_STEP, 6, secret, 100)
);
}
#[test]
fn totp1_tests() {
let secret: &[u8] = b"12345678901234567890";
assert_eq!(20, secret.len());
let pairs = vec![
("94287082", 59),
("07081804", 1111111109),
("14050471", 1111111111),
("89005924", 1234567890),
("69279037", 2000000000),
("65353130", 20000000000),
];
pairs.into_iter().for_each(|(expected, time)| {
assert_eq!(expected, totp::<Sha1>(secret, time));
});
}
#[test]
fn totp256_tests() {
let secret: &[u8] = b"12345678901234567890123456789012";
assert_eq!(32, secret.len());
let pairs = vec![
("46119246", 59),
("68084774", 1111111109),
("67062674", 1111111111),
("91819424", 1234567890),
("90698825", 2000000000),
("77737706", 20000000000),
];
pairs.into_iter().for_each(|(expected, time)| {
assert_eq!(expected, totp::<Sha256>(secret, time));
});
}
#[test]
fn totp512_tests() {
let secret: &[u8] = b"1234567890123456789012345678901234567890123456789012345678901234";
assert_eq!(64, secret.len());
let pairs = vec![
("90693936", 59),
("25091201", 1111111109),
("99943326", 1111111111),
("93441116", 1234567890),
("38618901", 2000000000),
("47863826", 20000000000),
];
pairs.into_iter().for_each(|(expected, time)| {
assert_eq!(expected, totp::<Sha512>(secret, time));
});
}
}