In testing this crate's TOTP implementation against Yubico Authenticator, I found that the (default, so 6-digit SHA-1) TOTP codes generated by Yubico Authenticator were not accepted by TOTP::is_valid(). I tested another implementation that I wrote many years ago in Python, whose codes did match Yubico Authenticator. I then came up with a Rust implementation that matches Yubico Authenticator and my older Python code:
fn totp_valid(secret: &[u8], code: &str, tolerance: u64) -> bool {
assert!(tolerance < 7);
let testing = u32::from_str(code).unwrap();
let anchor = SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.unwrap()
.as_secs()
/ 30;
let key = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, secret);
let mut tested = [false; 16];
for i in (7u64 - tolerance)..(8 + tolerance) {
let current = (anchor + i) - 8;
let tag = hmac::sign(&key, ¤t.to_be_bytes()[..]);
let offset = (tag.as_ref()[19] & 15) as usize;
let p = u32::from_be_bytes(tag.as_ref()[offset..offset + 4].try_into().unwrap());
tested[i as usize] = dbg!((p & 0x7fff_ffff) % 1_000_000) == testing;
}
tested.iter().fold(false, |acc, p| acc | p)
}
In testing this crate's TOTP implementation against Yubico Authenticator, I found that the (default, so 6-digit SHA-1) TOTP codes generated by Yubico Authenticator were not accepted by
TOTP::is_valid()
. I tested another implementation that I wrote many years ago in Python, whose codes did match Yubico Authenticator. I then came up with a Rust implementation that matches Yubico Authenticator and my older Python code: