Skip to content

Instantly share code, notes, and snippets.

@cynecx
Created September 28, 2023 00:38
Show Gist options
  • Select an option

  • Save cynecx/f87f089fb459a2a78dc34223deeed591 to your computer and use it in GitHub Desktop.

Select an option

Save cynecx/f87f089fb459a2a78dc34223deeed591 to your computer and use it in GitHub Desktop.
TOTP
use std::{
process::exit,
time::{SystemTime, UNIX_EPOCH},
};
use hmac::{Hmac, Mac};
use sha1::Sha1;
type HmacSha1 = Hmac<Sha1>;
fn hotp(key: &[u8], counter: u64) -> u32 {
let counter = counter.to_be_bytes();
let mut hmac = HmacSha1::new_from_slice(key).unwrap();
hmac.update(&counter);
let mac: [u8; 20] = hmac.finalize().into_bytes().into();
let offset = (mac[19] & 0xF) as usize;
let snum = u32::from_be_bytes(mac[offset..offset + 4].try_into().unwrap()) & 0x7fffffff;
snum % 1000000
}
fn totp(key: &[u8]) -> u32 {
let counter = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
/ 30;
hotp(key, counter)
}
fn decode_b32(val: &str) -> Option<Vec<u8>> {
let mut iter = val.bytes().map(|val| match val {
b'A'..=b'Z' => Some(val - b'A'),
b'2'..=b'7' => Some(26 + val - b'2'),
_ => None,
});
let mut decoded = Vec::with_capacity(val.len() * 5 / 8);
let mut bits = 5;
let mut state = iter.next()??;
for val in iter {
let val = val?;
if bits >= 3 {
let need = 8 - bits;
decoded.push(state << need | (val >> (5 - need)));
bits = 5 - need;
state = val & ((1 << bits) - 1);
} else {
bits += 5;
state = (state << 5) | val;
}
}
if bits == 8 {
decoded.push(state);
}
if decoded.is_empty() {
None
} else {
Some(decoded)
}
}
fn main() {
let Some(encoded_key) = std::env::args().nth(1) else {
eprintln!("missing totp key");
exit(1);
};
let Some(decoded_key) = decode_b32(&encoded_key) else {
eprintln!("invalid base32 encoded key");
exit(1);
};
println!("{}", totp(&decoded_key));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment