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