Rust实现TOTP无法生成正确2FA验证码问题求助
TOTP自定义实现生成验证码错误问题
我用Rust编写了TOTP的自定义实现,但使用给定密钥JBSWY3DPEHPK3PXP生成的2FA验证码不正确,当前输出为832112,预期结果是041374。以下是我的原始代码:
use std::{process::exit, time::{SystemTime, UNIX_EPOCH}}; use sha1::{Sha1, Digest}; fn hmac(key: Vec<u8>, text: Vec<u8>) -> Vec<u8>{ let mut key = key; const SIZE: usize = 64; if key.len() > SIZE { key = Sha1::digest(&key).to_vec(); } if key.len() < SIZE { for _ in 0..(SIZE-key.len()){ key.push(0); } } let ipad = 0x36; let opad = 0x5C; let mut ikeypad: Vec<u8> = key.iter().map(|val| {val^ipad}).collect(); let mut okeypad: Vec<u8> = key.iter().map(|val| {val^opad}).collect(); ikeypad.extend(&text); let inner_hash: Vec<u8> = Sha1::digest(&ikeypad).to_vec(); okeypad.extend(&inner_hash); let outer_hash: Vec<u8> = Sha1::digest(okeypad).to_vec(); return outer_hash; } fn totp(key: &String, size: u8, x: u16) { let time = 1000000; //SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let t = time / (x as u64); let mut time_vec: Vec<u8> = Vec::new(); for i in (0..8).rev(){ time_vec.push(((t & (0xFF << i*8)) >> i*8) as u8) } let hmac_vec = hmac(key.as_bytes().to_vec(), time_vec); let offset: u8 = hmac_vec[19] & 0xf; let hmac_truncated: Vec<u8> = hmac_vec[(offset as usize)..=((offset+3) as usize)].to_vec(); let code_num = ((hmac_truncated[0] as u64 & 0x7F) << 24) | ((hmac_truncated[1] as u64 & 0xFF) << 16) | ((hmac_truncated[2] as u64 & 0xFF) << 8) | ((hmac_truncated[3] as u64 & 0xFF)); dbg!(code_num % 10_u64.pow(size as u32)); } fn main() { totp(&"JBSWY3DPEHPK3PXP".to_string(), 6, 30); }
问题分析与修正
核心错误:Base32解码缺失
输入的密钥JBSWY3DPEHPK3PXP是Base32编码格式,但原始代码直接将其转为UTF-8字节传入HMAC函数,没有先解码为原始二进制密钥。这是导致验证码错误的根本原因。
其他优化点
- 用
resize替代手动循环补0,代码更简洁 - 时间戳字节生成逻辑优化,更直观地保证大端序(网络字节序)
- 函数参数改用
&str替代&String,符合Rust最佳实践 - 输出时补前导零,匹配预期的6位格式
修正后的完整代码
首先在Cargo.toml中添加Base32解码依赖:
[dependencies] sha1 = "0.10.5" base32 = "0.4.0"
然后修改代码:
use std::time::{SystemTime, UNIX_EPOCH}; use sha1::{Sha1, Digest}; use base32::{decode, Alphabet}; fn hmac(key: Vec<u8>, text: Vec<u8>) -> Vec<u8> { let mut key = key; const SIZE: usize = 64; if key.len() > SIZE { key = Sha1::digest(&key).to_vec(); } if key.len() < SIZE { key.resize(SIZE, 0); } let ipad = 0x36; let opad = 0x5C; let mut ikeypad: Vec<u8> = key.iter().map(|&val| val ^ ipad).collect(); let mut okeypad: Vec<u8> = key.iter().map(|&val| val ^ opad).collect(); ikeypad.extend(&text); let inner_hash = Sha1::digest(&ikeypad).to_vec(); okeypad.extend(&inner_hash); Sha1::digest(&okeypad).to_vec() } fn totp(key_base32: &str, size: u8, x: u16) -> u64 { // 解码Base32格式的密钥 let key = decode(Alphabet::RFC4648 { padding: true }, key_base32) .expect("无效的Base32密钥"); let time = 1000000; // SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let t = time / (x as u64); // 生成大端字节序的8字节时间戳 let mut time_vec = vec![0u8; 8]; for i in 0..8 { time_vec[i] = ((t >> ((7 - i) * 8)) & 0xFF) as u8; } let hmac_vec = hmac(key, time_vec); let offset = hmac_vec[19] & 0x0F; let start = offset as usize; let code_num = ((hmac_vec[start] as u64 & 0x7F) << 24) | ((hmac_vec[start + 1] as u64) << 16) | ((hmac_vec[start + 2] as u64) << 8) | (hmac_vec[start + 3] as u64); code_num % 10_u64.pow(size as u32) } fn main() { let code = totp("JBSWY3DPEHPK3PXP", 6, 30); dbg!(format!("{:06}", code)); // 输出:"041374" }
运行修正后的代码,即可得到预期的验证码041374。
内容的提问来源于stack exchange,提问作者Vanello1908
相关产品推荐
相关产品推荐

