Confused about encoding for TOTP-seed

Hello,

I am trying to build a simple tool that takes an initial TOTP-seed and outputs the 6-digit code for the current timestamp.
I used IT Tools - Handy online tools for developers to generate a seed/secret and test my program, but I get an invalid token. This is my code so far:

use totp_rs::{Algorithm, TOTP};
fn main() {
    // just some random seed, hardcoded for testing
    let seed = "HFNTDOQHB3OAGL6V".to_string();
    let token_generator = TOTP::new(
        Algorithm::SHA1,
        6,
        0,
        30,
        totp_rs::Secret::Raw(seed.as_bytes().to_vec())
            .to_bytes()
            .unwrap(),
    )
    .unwrap();

    print!("token: {}\n", token_generator.generate_current().unwrap());
}

and

[dependencies]
totp-rs = "5.6.0"

I also tried to use Secret::Encoded instead of Secret::Raw, but that only results in a panic from an unwrap.

I would greatly appreciate any assistance you can provide regarding this issue. Thank you in advance for your support!

totp-rs uses base32::decode(base32::Alphabet::Rfc4648 { padding: false }, value.as_ref()) to decode a secret from a otpauth:// url. You probably need to do the same.

Thanks for your suggestion, but i still get an incorrect token-value. I changed the seed-value because the original gave an error:

thread 'main' panicked at src/main.rs:15:6:
called Result::unwrap() on an Err value: SecretSize(80)

I think this is weird because the original seed should be valid because it was created on the mentioned website. The docs say:

TotpUrlError::SecretSize(usize)
Invalid secret size. (Too short?)

This is my code (with the larger seed-value):

use base32::{Alphabet, decode};
use totp_rs::{Algorithm, TOTP};

fn main() {
    // just some random seed, hardcoded for testing
    let seed = "BFHLRRVHFWKX6A32LKASDJALSJDKLSD".to_string();

    let token_generator = TOTP::new(
        Algorithm::SHA1,
        6,
        0,
        30,
        decode(Alphabet::Rfc4648 { padding: false }, &seed).unwrap(),
    )
    .unwrap();

    print!("token: {}\n", token_generator.generate_current().unwrap());
}

I still think it is some kind of encoding issue. I also double-checked the time settings on my system to no avail.

The totp-rs crate enforces the secret size >= 128 since version 3.0. If you use 2.0.1 it will work with the shorter secret. (You'll have to tweak the TOTP::new() call, which has extra arguments, and convert the secret to raw bytes beforehand.)

The minimum secret size constraint is unfortunate, despite being RFC compliant, since all those authenticator apps use 80-bit secrets to this day.

Thanks for the insights. As I said, it was fixed by just entering a larger value.
But my main problem still remains: the tokens that my program outputs do not match the tokens of the website.

Well, this program:

use base32::{decode, Alphabet};
use totp_rs::{Algorithm, TOTP};

fn main() {
    let seed = "3HLTJAD6F4ONB35W".to_string();
    let token_generator = TOTP::new(
        Algorithm::SHA1, 6, 0, 30,
        decode(Alphabet::Rfc4648 { padding: false }, &seed).unwrap(),
        None, "Test".to_owned()
    )
    .unwrap();

    print!("token: {}\n", token_generator.generate_current().unwrap());
}   

...works for me with totp-rs 2.0.1 and gives the same results as the referenced online TOTP generator.

1 Like

You are right. There might be an issue with the website then. I took your secret:

3HLTJAD6F4ONB35W

and inserted it into my KeePass as well. My program, the website and my KeePass all give the same token. But when i use the larger secret:

BFHLRRVHFWKX6A32LKASDJALSJDKLSD

which would also satisfy the rule of a secret with >=128bit, then my program and KeePass give the same token. The website outputs a different one.

I guess that solves my problem. The main issue was solved by @bjorn3 then. It was just the encoding.

Thank you both @inejge and @bjorn3 for your guidance

2 Likes