1#![doc(html_root_url = "https://docs.rs/totp-lite/2.0.1")]
39
40use digest::{
41 block_buffer::Eager,
42 core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore},
43 generic_array::typenum::{IsLess, Le, NonZero, U256},
44 FixedOutput, HashMarker, Update,
45};
46use hmac::{Hmac, Mac};
47pub use sha1::Sha1;
48pub use sha2::{Sha256, Sha512};
49
50pub const DEFAULT_STEP: u64 = 30;
52
53pub const DEFAULT_DIGITS: u32 = 8;
55
56pub fn totp<H>(secret: &[u8], time: u64) -> String
75where
76 H: Update + FixedOutput + CoreProxy,
77 H::Core: HashMarker
78 + UpdateCore
79 + FixedOutputCore
80 + BufferKindUser<BufferKind = Eager>
81 + Default
82 + Clone,
83 <H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
84 Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
85{
86 totp_custom::<H>(DEFAULT_STEP, DEFAULT_DIGITS, secret, time)
87}
88
89pub fn totp_custom<H>(step: u64, digits: u32, secret: &[u8], time: u64) -> String
106where
107 H: Update + FixedOutput + CoreProxy,
108 H::Core: HashMarker
109 + UpdateCore
110 + FixedOutputCore
111 + BufferKindUser<BufferKind = Eager>
112 + Default
113 + Clone,
114 <H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
115 Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
116{
117 let mut mac = <Hmac<H> as Mac>::new_from_slice(secret).unwrap();
119 <Hmac<H> as Update>::update(&mut mac, &to_bytes(time / step));
120 let hash: &[u8] = &mac.finalize().into_bytes();
121
122 let offset: usize = (hash.last().unwrap() & 0xf) as usize;
124 let binary: u64 = (((hash[offset] & 0x7f) as u64) << 24)
125 | ((hash[offset + 1] as u64) << 16)
126 | ((hash[offset + 2] as u64) << 8)
127 | (hash[offset + 3] as u64);
128
129 format!("{:01$}", binary % (10_u64.pow(digits)), digits as usize)
130}
131
132fn to_bytes(n: u64) -> [u8; 8] {
134 let mask = 0x00000000000000ff;
135 let mut bytes: [u8; 8] = [0; 8];
136 (0..8).for_each(|i| bytes[7 - i] = (mask & (n >> (i * 8))) as u8);
137 bytes
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn to_bytes_test() {
146 assert_eq!(vec![0, 0, 0, 0, 0, 0, 0, 1], to_bytes(59 / DEFAULT_STEP));
147 assert_eq!(
148 vec![0, 0, 0, 0, 0x02, 0x35, 0x23, 0xec],
149 to_bytes(1111111109 / DEFAULT_STEP)
150 );
151 assert_eq!(
152 vec![0, 0, 0, 0, 0x27, 0xbc, 0x86, 0xaa],
153 to_bytes(20000000000 / DEFAULT_STEP)
154 );
155 }
156
157 #[test]
158 fn variable_length() {
159 let secret: &[u8] = b"12345678901234567890123456789012";
160 assert_eq!(
161 "2102975832",
162 totp_custom::<Sha256>(DEFAULT_STEP, 10, secret, 100)
163 );
164 assert_eq!(
165 "975832",
166 totp_custom::<Sha256>(DEFAULT_STEP, 6, secret, 100)
167 );
168 }
169
170 #[test]
171 fn totp1_tests() {
172 let secret: &[u8] = b"12345678901234567890";
173 assert_eq!(20, secret.len());
174
175 let pairs = vec![
176 ("94287082", 59),
177 ("07081804", 1111111109),
178 ("14050471", 1111111111),
179 ("89005924", 1234567890),
180 ("69279037", 2000000000),
181 ("65353130", 20000000000),
182 ];
183
184 pairs.into_iter().for_each(|(expected, time)| {
185 assert_eq!(expected, totp::<Sha1>(secret, time));
186 });
187 }
188
189 #[test]
190 fn totp256_tests() {
191 let secret: &[u8] = b"12345678901234567890123456789012";
192 assert_eq!(32, secret.len());
193
194 let pairs = vec![
195 ("46119246", 59),
196 ("68084774", 1111111109),
197 ("67062674", 1111111111),
198 ("91819424", 1234567890),
199 ("90698825", 2000000000),
200 ("77737706", 20000000000),
201 ];
202
203 pairs.into_iter().for_each(|(expected, time)| {
204 assert_eq!(expected, totp::<Sha256>(secret, time));
205 });
206 }
207
208 #[test]
209 fn totp512_tests() {
210 let secret: &[u8] = b"1234567890123456789012345678901234567890123456789012345678901234";
211 assert_eq!(64, secret.len());
212
213 let pairs = vec![
214 ("90693936", 59),
215 ("25091201", 1111111109),
216 ("99943326", 1111111111),
217 ("93441116", 1234567890),
218 ("38618901", 2000000000),
219 ("47863826", 20000000000),
220 ];
221
222 pairs.into_iter().for_each(|(expected, time)| {
223 assert_eq!(expected, totp::<Sha512>(secret, time));
224 });
225 }
226}