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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
use crate::constants::{ DEFAULT_BREADTH, DEFAULT_COUNTER, DEFAULT_DIGITS };
use hmacsha1::hmac_sha1;
fn u64_to_8_length_u8_array(input: u64) -> [u8; 8] {
let mut bytes = [0_u8; 8];
for (i, item) in bytes.iter_mut().enumerate().take(7) {
*item = (input >> (i * 8)) as u8;
}
bytes.reverse();
bytes
}
fn make_opt(secret: &[u8], digits: u32, counter: u64) -> String {
let counter_bytes = u64_to_8_length_u8_array(counter);
let digest = hmac_sha1(secret, &counter_bytes);
let offset = digest[digest.len() - 1] as usize & 0x0f;
let value = (u32::from(digest[offset]) & 0x7f) << 24
| (u32::from(digest[offset + 1]) & 0xff) << 16
| (u32::from(digest[offset + 2]) & 0xff) << 8
| u32::from(digest[offset + 3]) & 0xff;
let mut code = (value % 10_u32.pow(digits)).to_string();
if code.len() != (digits as usize) {
code = "0".repeat((digits - (code.len() as u32)) as usize) + &code;
}
code
}
pub enum HotpMakeOption {
Default,
Counter(u64),
Digits(u32),
Full {
counter: u64,
digits: u32,
},
}
pub enum HotpCheckOption {
Default,
Counter(u64),
Breadth(u64),
Full {
counter: u64,
breadth: u64,
},
}
pub struct Hotp(pub String);
impl Hotp {
pub fn make(&self, options: HotpMakeOption) -> String {
match options {
HotpMakeOption::Default => make_opt(self.0.as_bytes(), DEFAULT_DIGITS, DEFAULT_COUNTER),
HotpMakeOption::Counter(counter) => make_opt(self.0.as_bytes(), DEFAULT_DIGITS, counter),
HotpMakeOption::Digits(digits) => make_opt(self.0.as_bytes(), digits, DEFAULT_COUNTER),
HotpMakeOption::Full {
counter,
digits,
} => make_opt(self.0.as_bytes(), digits, counter),
}
}
pub fn check(&self, otp: &str, options: HotpCheckOption) -> bool {
let (counter, breadth) = match options {
HotpCheckOption::Default => (DEFAULT_COUNTER, DEFAULT_BREADTH),
HotpCheckOption::Counter(counter) => (counter, DEFAULT_BREADTH),
HotpCheckOption::Breadth(breadth) => (DEFAULT_COUNTER, breadth),
HotpCheckOption::Full {
counter,
breadth,
} => (counter, breadth),
};
for i in (counter - breadth)..=(counter + breadth) {
let code = self.make(HotpMakeOption::Full {
counter: i,
digits: otp.len() as u32,
});
if code == otp {
return true;
}
}
false
}
}
#[cfg(test)]
mod tests {
use super::{Hotp, HotpMakeOption};
#[test]
fn make() {
let hotp = Hotp(String::from("test"));
let code1 = hotp.make(HotpMakeOption::Default);
let code2 = hotp.make(HotpMakeOption::Default);
assert_eq!(code1, code2);
}
}