为何基于指定密钥生成的TOTP结果不正确?Rust代码排查求助
问题描述
我使用以下Rust代码基于密钥JBSWY3DPEHPK3PXP生成TOTP:
use hmac::{Hmac, Mac}; use sha1::Sha1; type hmacsha1 = Hmac<Sha1>; fn main() { let key = "JBSWY3DPEHPK3PXP"; let step: u32 = 30; let skew: u32 = 1; let mut hmac = hmacsha1::new_from_slice(key.as_bytes()).unwrap(); let time = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(); let time = time as u32 / step; hmac.update(&time.to_be_bytes()); let mut result = hmac.finalize().into_bytes().to_vec(); let offset = result.last().unwrap() & 15; let binary = (result[offset as usize] as u32 & 0x7f) << 24 | (result[(offset + 1) as usize] as u32 & 0xff) << 16 | (result[(offset + 2) as usize] as u32 & 0xff) << 8 | (result[(offset + 3) as usize] as u32 & 0xff); let mut fin = binary % 10_u32.pow(6); let fin_str = fin.to_string(); println!("{:?}", result.last()); println!("{:?}", fin_str); }
我已遵循TOTP参考文档并参照Java示例实现,但运行后无法得到预期的TOTP结果。恳请帮忙排查问题所在,获取正确结果。
问题排查与修复
你的代码存在两个核心问题:
- 密钥未做Base32解码:
JBSWY3DPEHPK3PXP是Base32编码格式的密钥,TOTP要求使用原始二进制密钥进行HMAC计算,直接传入字符串字节会导致密钥错误。 - 时间戳类型转换溢出:
as_secs()返回u64类型,直接转为u32会在当前时间(2024年)出现溢出,导致时间步长计算错误。
修复后的完整代码如下:
use hmac::{Hmac, Mac}; use sha1::Sha1; use base32::decode; type HmacSha1 = Hmac<Sha1>; fn main() { let key_base32 = "JBSWY3DPEHPK3PXP"; let step: u64 = 30; // 解码Base32格式的密钥,得到原始二进制数据 let key = decode(base32::Alphabet::RFC4648 { padding: true }, key_base32) .expect("无效的Base32密钥"); let time = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(); // 使用u64计算时间步长,避免溢出,符合TOTP的64位时间戳要求 let time_step = time / step; let mut hmac = HmacSha1::new_from_slice(&key).unwrap(); // 将时间步长转为8字节大端序 hmac.update(&time_step.to_be_bytes()); let result = hmac.finalize().into_bytes(); let offset = (result.last().unwrap() & 0x0f) as usize; let binary = ((result[offset] & 0x7f) as u32) << 24 | ((result[offset + 1] & 0xff) as u32) << 16 | ((result[offset + 2] & 0xff) as u32) << 8 | ((result[offset + 3] & 0xff) as u32); let totp = binary % 1_000_000; // 确保输出固定6位数字,不足补前导零 let totp_str = format!("{:06}", totp); println!("TOTP: {}", totp_str); }
需要在Cargo.toml中添加对应依赖:
[dependencies] hmac = "0.12" sha1 = "0.10" base32 = "0.4"
关键修改说明
- 引入
base32库解码密钥,还原TOTP要求的原始二进制密钥。 - 将时间相关变量改为
u64类型,避免溢出问题,同时符合TOTP标准的64位时间戳要求。 - 使用
format!("{:06}", totp)保证输出固定6位数字,补全前导零(原始代码中结果不足6位时会缺失数字,不符合TOTP规范)。 - 优化变量命名和代码结构,提升可读性。
内容的提问来源于stack exchange,提问作者Toby
相关产品推荐
相关产品推荐

