otp 0.2.0

Pure rust implementation of Hash- and Time-based One Time Passwords in Rust.
Documentation

use std::time::{SystemTime,Duration};

use sha1::Sha1;
use hmac::{Hmac, Mac};

pub fn hotp( secret: &[u8], counter: u64) -> u32 {
  // build hmac key from counter
  let hmac_message: &[u8; 8] = &[
    ( ( counter >> 56 ) & 0xff ) as u8,
    ( ( counter >> 48 ) & 0xff ) as u8,
    ( ( counter >> 40 ) & 0xff ) as u8,
    ( ( counter >> 32 ) & 0xff ) as u8,
    ( ( counter >> 24 ) & 0xff ) as u8,
    ( ( counter >> 16 ) & 0xff ) as u8,
    ( ( counter >> 8 ) & 0xff ) as u8,
    ( ( counter >> 0 ) & 0xff ) as u8,
    ];

    // hmac the key and secret
    let hash = hmac_sha1(secret, hmac_message);

    // calculate the dynamic offset for the value
    let dynamic_offset = ( hash[19] & (0xf as u8) ) as usize ;

    // build the u32 code from the hash
    ( ( ( hash[dynamic_offset] as u32 ) & 0x7f ) << 24
    | ( hash[dynamic_offset+1] as u32 ) << 16
    | ( hash[dynamic_offset+2] as u32 ) << 8
    | ( hash[dynamic_offset+3] as u32 )
    ) as u32
}

pub fn hotp_validate(secret: &[u8], counter: u64, guess: u32, guess_digits: u32) -> bool {
    guess == ( hotp(secret, counter) % 10u32.pow(guess_digits) )
}

pub fn totp( secret: &[u8], time: SystemTime, window: Duration) -> u32 {
    let counter: u64 = 
        time.duration_since(SystemTime::UNIX_EPOCH)
            .expect("We should never be getting authentication requests 50 years in the past")
            .as_secs()
            / window.as_secs();

    hotp(secret, counter)
}

pub fn totp_validate( secret: &[u8], time: SystemTime, window_seconds: Duration, guess: u32,
                       guess_digits: u32) -> bool {
    guess == ( totp(secret, time, window_seconds) % 10u32.pow(guess_digits) )
}


fn hmac_sha1(key: &[u8], message: &[u8]) -> [u8;20] {
    // Create the hasher with the key. We can use expect for Hmac algorithms as they allow arbitrary key sizes.
    let mut hasher: Hmac<Sha1> = Mac::new_from_slice(key) .expect("HMAC algoritms can take keys of any size");

    // hash the message
    hasher.update(message);

    // finalize the hash and convert to a static array
    hasher.finalize().into_bytes().into()
}

#[cfg(test)]
mod tests {
    use super::*;

    // "12345678901234567890"
    const HOTP_TEST_SECRET: &'static[u8] = &[
        0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
        0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
    ];

    const HOTP_TEST_VALUES: [u32; 10] = [
        1284755224,
        1094287082,
        137359152,
        1726969429,
        1640338314,
        868254676,
        1918287922,
        82162583,
        673399871,
        645520489,
    ];

    #[test]
    fn test_hotp() {
        for counter in 0..10_usize {
            if HOTP_TEST_VALUES[counter] !=  hotp(HOTP_TEST_SECRET, counter as u64 ) {
                panic!("Expected {} for counter {}, found {}.", HOTP_TEST_VALUES[counter], counter,
                    hotp(HOTP_TEST_SECRET, counter as u64) );
            }
        }
    }

    #[test]
    fn test_hotp_4_digits() {
        for counter in 0..10_usize {
            if !hotp_validate(HOTP_TEST_SECRET, counter as u64 , HOTP_TEST_VALUES[counter] % 10u32.pow(4), 4) {
                panic!("Error validating code {} for counter {}.", HOTP_TEST_VALUES[counter] % 10u32.pow(4), counter);
            }
        }
    }

    #[test]
    fn test_totp() {
        let window: Duration = Duration::from_secs(30);
        let mut time: SystemTime = SystemTime::UNIX_EPOCH;
       for counter in 0..10_usize {
           if HOTP_TEST_VALUES[counter] !=  totp(HOTP_TEST_SECRET, time, window ) {
               panic!("Expected {} for counter {}, found {}.", HOTP_TEST_VALUES[counter], counter,
                    hotp(HOTP_TEST_SECRET, counter as u64) );
           }
           time += window;
       }
    }

    #[test]
    fn test_totp_4_digits() {
        let window: Duration = Duration::from_secs(30);
        let mut time: SystemTime = SystemTime::UNIX_EPOCH;
        for counter in 0..10_usize {
            if !totp_validate(HOTP_TEST_SECRET, time, window, HOTP_TEST_VALUES[counter] % 10u32.pow(4), 4) {
                panic!("Error validating code {} for counter {}.", HOTP_TEST_VALUES[counter] % 10u32.pow(4), counter);
            }
            if totp_validate(HOTP_TEST_SECRET, time+window, window, HOTP_TEST_VALUES[counter] % 10u32.pow(4), 4) {
                panic!("Error incorrectly validating code {} for counter {}.", HOTP_TEST_VALUES[counter] % 10u32.pow(4), counter+1);
            }
            time += window;
        }
    }
}