Реализация TOTP на Rust

Я пишу собственную реализацию TOTP на Rust, но не могу получить правильный код 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);
    
}

Я попробовал все. И найдите документацию RFC о TOTP и HOTP, перепишите методы и поищите в Интернете другие реализации. По этим данным у меня 832112, а должно 041374

Можете ли вы указать ожидаемый и фактический результат за фиксированное время вместо SystemTime::now()? Отсутствие ничего для тестирования не очень удобно для отладки.

cafce25 28.02.2024 18:36

Во-первых, я проверяю вывод одновременно с помощью библиотеки Python pyotp. Во-вторых, когда я устанавливаю время на 1000000 секунд, у меня есть «832112», а должно быть «041374».

Vanello1908 28.02.2024 18:42

Необходимость использования to_string возникла из-за плохой практики использования &String вместо &str в качестве типа параметра, чего практически никогда не следует делать.

cafce25 29.02.2024 08:32
SQL Injection: Атаки в реальной жизни и как это вредит бизнесу
SQL Injection: Атаки в реальной жизни и как это вредит бизнесу
Один-единственный вредоносный запрос может нанести ущерб вашему бизнесу. Уязвимости вашего кода могут привести к:
0
3
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Обычно ключ 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 и некоторые другие реализации поддерживают.

Другие вопросы по теме