1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
extern crate hmacsha1; extern crate byteorder; use std::io::Cursor; use hmacsha1::{hmac_sha1, SHA1_DIGEST_BYTES}; use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; use std::time; pub fn hotp(key: &[u8], counter: u64, digits: u32) -> u32 { let mut counter_bytes = vec![]; counter_bytes.write_u64::<BigEndian>(counter).unwrap(); let hmac = hmac_sha1(key, &counter_bytes); let dyn_offset = (hmac[SHA1_DIGEST_BYTES-1] & 0xf) as usize; let dyn_range = &hmac[dyn_offset..dyn_offset+4]; let mut rdr = Cursor::new(dyn_range); let s_num = rdr.read_u32::<BigEndian>().unwrap() & 0x7fffffff; s_num % 10u32.pow(digits) } const DIGITS: u32 = 6; const TIME_STEP: u64 = 30; #[derive(Debug)] pub struct TotpSlot { pub code: u32, pub secs_left: u32, } pub fn totp_offset(key: &[u8], slot_offset: i32) -> TotpSlot { let now = time::SystemTime::now().duration_since(time::UNIX_EPOCH).expect("Current time is before unix epoch"); let slot = (now.as_secs()/TIME_STEP) as i64 + slot_offset as i64; let code = hotp(key, slot as u64, DIGITS); let secs_left = (((slot_offset+1) as u64)*TIME_STEP - now.as_secs()%TIME_STEP) as u32; TotpSlot { code, secs_left, } } pub fn totp(key: &[u8]) -> u32 { let now = time::SystemTime::now().duration_since(time::UNIX_EPOCH).expect("Current time is before unix epoch"); let slot = now.as_secs()/TIME_STEP; hotp(key, slot, DIGITS) } #[cfg(test)] mod tests { use super::hotp; const KEY : &'static [u8] = b"12345678901234567890"; const DIGITS: u32 = 6; #[test] fn test_hotp() { assert_eq!(hotp(KEY, 0, DIGITS), 755224); assert_eq!(hotp(KEY, 1, DIGITS), 287082); assert_eq!(hotp(KEY, 2, DIGITS), 359152); assert_eq!(hotp(KEY, 3, DIGITS), 969429); assert_eq!(hotp(KEY, 4, DIGITS), 338314); assert_eq!(hotp(KEY, 5, DIGITS), 254676); assert_eq!(hotp(KEY, 6, DIGITS), 287922); } }