basic_otp/
lib.rs

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