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

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函数,没有先解码为原始二进制密钥。这是导致验证码错误的根本原因。

其他优化点

  1. 用resize替代手动循环补0,代码更简洁
  2. 时间戳字节生成逻辑优化,更直观地保证大端序(网络字节序)
  3. 函数参数改用&str替代&String,符合Rust最佳实践
  4. 输出时补前导零,匹配预期的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 22:25:55