You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何基于指定密钥生成的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"

关键修改说明

  1. 引入base32库解码密钥,还原TOTP要求的原始二进制密钥。
  2. 将时间相关变量改为u64类型,避免溢出问题,同时符合TOTP标准的64位时间戳要求。
  3. 使用format!("{:06}", totp)保证输出固定6位数字,补全前导零(原始代码中结果不足6位时会缺失数字,不符合TOTP规范)。
  4. 优化变量命名和代码结构,提升可读性。

内容的提问来源于stack exchange,提问作者Toby

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 19:22:44