TOTP Generator with HMAC-SHA1
Derive RFC 6238 time-based one-time codes from a Base32 secret, with counter packing, dynamic truncation, and modulo digits.
Code
use hmac::{Hmac, Mac};
use sha1::Sha1;
type HmacSha1 = Hmac<Sha1>;
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
fn base32_decode(input: &str) -> Option<Vec<u8>> {
let mut buffer: u32 = 0;
let mut bits: u8 = 0;
let mut out = Vec::new();
for ch in input.bytes().filter(|byte| *byte != b'=') {
let value = ALPHABET
.iter()
.position(|candidate| *candidate == ch.to_ascii_uppercase())? as u32;
buffer = (buffer << 5) | value;
bits += 5;
if bits >= 8 {
bits -= 8;
out.push((buffer >> bits) as u8);
buffer &= (1 << bits) - 1;
}
}
Some(out)
}
fn hotp(secret: &[u8], counter: u64, digits: u32) -> u32 {
let mut mac = HmacSha1::new_from_slice(secret).expect("HMAC accepts any key length");
mac.update(&counter.to_be_bytes());
let digest = mac.finalize().into_bytes();
let offset = (digest[19] & 0x0f) as usize;
let code = u32::from_be_bytes([
digest[offset],
digest[offset + 1],
digest[offset + 2],
digest[offset + 3],
]) & 0x7fff_ffff;
code % 10u32.pow(digits)
}
fn totp(secret: &[u8], unix_time: u64, step: u64, digits: u32) -> u32 {
hotp(secret, unix_time / step, digits)
}
fn main() {
let secret = base32_decode("JBSWY3DPEHPK3PXP").expect("valid base32");
let code = totp(&secret, 1_700_000_000, 30, 6);
println!("code: {:06}", code);
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.