TOTP 在 Rust 上的实现

问题描述 投票:0回答:1

我正在 Rust 上编写自己的 TOTP 实现,但我无法使用给定的秘密获得正确的 2FA 代码。

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);
    
}

我什么都试过了。并发现有关 TOTP 和 HOTP 的 RFC 文档,重写方法,并通过互联网查找其他实现。 有了这个数据,我有 832112,而应该是 041374

security rust hash totp
1个回答
0
投票

通常,HMAC 密钥(TOTP 密钥)以 Base32 字符串形式给出,此处的情况就是如此。您需要将其解码为实际的字节字符串,而不是在编码时将其用作密钥。

这是代码的稍微修改版本,它使用 HMAC 板条箱并将

totp
代码实现为纯函数,这允许我们使用来自 RFC 的测试向量 :

use hmac::Mac;
use sha1::Sha1;

fn totp(key: &str, time: u64, size: u32, x: u16) -> u64 {
    let key = base32::decode(base32::Alphabet::RFC4648 { padding: false }, key).unwrap();
    let t = time / (x as u64);
    let timebuf = t.to_be_bytes();
    let mut h = hmac::Hmac::<Sha1>::new_from_slice(&key).unwrap();
    h.update(&timebuf);
    let hmac_vec = h.finalize().into_bytes();
    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 = u32::from_be_bytes(hmac_truncated.try_into().unwrap()) & 0x7fffffff;
    (code_num as u64) % 10_u64.pow(size)
}
fn main() {
    let res = totp("JBSWY3DPEHPK3PXP", 1000000, 6, 30);
    dbg!(res);
}

#[cfg(test)]
mod tests {
    use super::totp;

    #[test]
    fn known_values() {
        assert_eq!(
            totp("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", 59, 8, 30),
            94287082
        );
        assert_eq!(
            totp("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", 1111111109, 8, 30),
            07081804
        );
    }
}

我还随意使用 Rust 提供的本机字节序函数,而不是手动实现它们。这更容易理解,而且速度也更快,因为在许多系统上,有用于字节交换和转换的单个指令。

如果您需要,基于 SHA-256 和 SHA-512 的实现的实现将作为读者的练习。 Google Authenticator 不支持它们,但 Authy 和其他一些实现支持。

© www.soinside.com 2019 - 2024. All rights reserved.