Skip to content

Instantly share code, notes, and snippets.

@jdmichaud
Created February 26, 2024 15:27
Show Gist options
  • Save jdmichaud/fb2abbe7395c7033d9118efe247f1bab to your computer and use it in GitHub Desktop.
Save jdmichaud/fb2abbe7395c7033d9118efe247f1bab to your computer and use it in GitHub Desktop.
TOTP client in Rust
// https://rednafi.com/go/totp_client/
// https://datatracker.ietf.org/doc/html/rfc6238
use sha1::{Sha1};
use hmac::{Hmac, Mac};
use std::time::{SystemTime, UNIX_EPOCH};
fn generate_totp(secret_key: &str, timestamp: u64) -> u32 {
// The base32 encoded secret key string is decoded to a byte slice
let secret_bytes = base32::decode(
base32::Alphabet::RFC4648 { padding: true },
&secret_key.trim().to_ascii_uppercase()
).unwrap();
// 30 seconds is the timestep as defined in rfc6238
let time_bytes = (timestamp / 30).to_be();
// The timestamp bytes are concatenated with the decoded secret key
// bytes. Then a 20-byte SHA-1 hash is calculated from the byte slice
let mut mac0 = <Hmac<Sha1> as Mac>::new_from_slice(&secret_bytes).unwrap();
mac0.update(&time_bytes.to_ne_bytes());
let result = mac0.finalize().into_bytes();
// and the SHA-1 with 0x0F (15) to get a single-digit offset
let offset: usize = (result[result.len() - 1] & 0x0F) as usize;
// Truncate the SHA-1 by the offset and convert it into a 32-bit
// unsigned int. AND the 32-bit int with 0x7FFFFFFF (2147483647)
// to get a 31-bit unsigned int.
let truncated_hash = u32::from_ne_bytes(result[offset..offset + 4].try_into().unwrap()) & 0x7FFFFFFF;
// Take modulo 1_000_000 to get a 6-digit code
truncated_hash % 1_000_000
}
fn main() {
let unix_epoch = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
println!("{:06}", generate_totp("KRCVGVCUIVJVIRKT", unix_epoch.as_secs()));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment