Use last byte of HMAC output for truncating This is to use the least significant byte of the HMAC regardless of size. RFC 6328 (TOTP) Section 1.2 says you can use SHA-1 SHA-256 or SHA-512 with the same algorithm of RFC 4226 (HTOP). This seems ok until you realize that all the new HMACs have different output sizes and HTOP only expects a 20 byte fixed MAC. It is not completely clear if RFC 4226 Section 5.3 means "get the bottom 4 bits from byte at offset 19" or "get the 4 least significant bits". Other implementations (https://github.com/pyauth/pyotp/blob/6568c1a83af8e0229f3c4b28d03552d601e2b7fe/src/pyotp/otp.py#L28) and Wikipedia read the "Dynamic Truncation" algorithm to be the last 4 bits of the MAC, so I think this implementation should follow the others.
diff --git a/src/lib.rs b/src/lib.rs
index 1ca07dc..8bb2c94 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -140,7 +140,7 @@ impl<T: AsRef<[u8]>> TOTP<T> {
/// Will generate a token according to the provided timestamp in seconds
pub fn generate(&self, time: u64) -> String {
let result: &[u8] = &self.sign(time);
- let offset = (result[19] & 15) as usize;
+ let offset = (result.last().unwrap() & 15) as usize;
let result = u32::from_be_bytes(result[offset..offset + 4].try_into().unwrap()) & 0x7fff_ffff;
format!(
"{1:00$}",
@@ -244,13 +244,13 @@ mod tests {
#[test]
fn generates_token_sha256() {
let totp = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecret");
- assert_eq!(totp.generate(1000).as_str(), "423657");
+ assert_eq!(totp.generate(1000).as_str(), "480200");
}
#[test]
fn generates_token_sha512() {
let totp = TOTP::new(Algorithm::SHA512, 6, 1, 1, "TestSecret");
- assert_eq!(totp.generate(1000).as_str(), "416767");
+ assert_eq!(totp.generate(1000).as_str(), "850500");
}
#[test]