我正在 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
通常,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 和其他一些实现支持。