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);
    }
}