totp-rs 6.0.0

RFC-compliant TOTP implementation with ease of use as a goal and additional QoL features.
Documentation

totp-rs

Build Status docs crates.io codecov cargo-audit

This library permits the creation of 2FA authentification tokens per TOTP, the verification of said tokens, with configurable time skew, validity time of each token, algorithm and number of digits! Default features are kept as lightweight as possible to ensure small binaries and short compilation time.

It now supports parsing otpauth URLs into a totp object, with sane default values.

Be aware that some authenticator apps will accept the SHA256 and SHA512 algorithms but silently fallback to SHA1 which will make the check() function fail due to mismatched algorithms.

Features


qr

With optional feature "qr", you can use it to generate a base64 png qrcode. This will enable feature otpauth.

otpauth

With optional feature "otpauth", support parsing the TOTP parameters from an otpauth URL, and generating an otpauth URL. It adds 2 fields to Totp.

serde

With optional feature "serde", library-defined types Totp, Algorithm and Secret will be Deserialize-able and Serialize-able.

gen_secret

With optional feature "gen_secret", a secret will be generated for you to store in database.

zeroize

Securely zero secret information when the TOTP struct is dropped.

steam

Add support for Steam TOTP tokens.

migration

Enabled by default. Provides deprecated aliases and shims for the 5.7.x API so existing code keeps compiling while you port it to the 6.0 Builder API (see MIGRATION.md). Disable it with default-features = false once you have finished migrating to turn any remaining 5.x calls into hard errors.

std

Enabled by default, and enables alloc. Gives access to the methods that read the system clock: generate_current, check_current, ttl and next_step_current. Without it, pass the current Unix timestamp yourself to generate, check and next_step.

alloc

Enabled by std, otpauth, qr and migration. Required by everything that allocates: the base32 conversions (Secret::try_from_base32 and Secret::to_base32), the String conversions of Algorithm, and secrets of any length other than 20 bytes.

Examples

Summary

  1. Understanding Secret
  2. Generate a token
  3. Enable qrcode generation
  4. Enable otpauth url support
  5. Enable gen_secret support
  6. With RFC-6238 compliant default
  7. New TOTP from steam secret
  8. Using it without std

Understanding Secret


Secret is an opaque type holding the secret's raw bytes. You can build one either from raw bytes or from a base32-encoded string; both yield the same value.

Secret::from("TestSecretSuperSecret".as_bytes())

Is equivalent to:

Secret::try_from_base32("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap()

Generate a token


Add it to your Cargo.toml:

[dependencies]
totp-rs = "^6.0"

You can then do something like:

use totp_rs::{Builder, Secret, Totp};

fn main() {
    let totp: Totp = Builder::new()
        .with_secret(Secret::from("TestSecretSuperSecret".as_bytes()))
        .build()
        .unwrap();
    let token = totp.generate_current();
    println!("{}", token);
}

Which is equivalent to:

use totp_rs::{Builder, Secret, Totp};

fn main() {
    let totp: Totp = Builder::new()
        .with_secret(Secret::try_from_base32("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap())
        .build()
        .unwrap();
    let token = totp.generate_current();
    println!("{}", token);
}

generate_current returns a Token. It implements Display, so it prints directly; call .to_string() if you need an owned String.

With qrcode generation


Add it to your Cargo.toml:

[dependencies.totp-rs]
version = "^6.0"
features = ["qr"]

You can then do something like:

use totp_rs::{Builder, Secret, Totp};

fn main() {
    let totp: Totp = Builder::new()
        .with_secret(Secret::try_from_base32("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap())
        .with_issuer(Some("Github"))
        .with_account_name("constantoine@github.com")
        .build()
        .unwrap();
    let qr_code = totp.to_qr_base64().unwrap();
    println!("{}", qr_code);
}

With otpauth url support


Add it to your Cargo.toml:

[dependencies.totp-rs]
version = "^6.0"
features = ["otpauth"]

You can then do something like:

use totp_rs::Totp;

fn main() {
    let otpauth = "otpauth://totp/GitHub:constantoine@github.com?secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&issuer=GitHub";
    let totp = Totp::from_url(otpauth).unwrap();
    println!("{}", totp.generate_current());
}

With gen_secret


Add it to your Cargo.toml:

[dependencies.totp-rs]
version = "^6.0"
features = ["gen_secret"]

With gen_secret, Builder::new() already holds a freshly generated secret, so you can build a Totp without supplying one:

use totp_rs::{Builder, Totp};

fn main() {
    let totp: Totp = Builder::new()
        .build()
        .unwrap();
    let token = totp.generate_current();
    println!("{}", token);
}

Which is equivalent to setting the secret explicitly:

use totp_rs::{Builder, Secret, Totp};

fn main() {
    let totp: Totp = Builder::new()
        .with_secret(Secret::generate())
        .build()
        .unwrap();
    let token = totp.generate_current();
    println!("{}", token);
}

With RFC-6238 compliant default


Builder::new() already starts from RFC-6238 compliant defaults (SHA1, 6 digits, skew of 1, 30 second step), and build() enforces that compliance. So the only mandatory step is providing a secret:

use totp_rs::{Builder, Secret, Totp};

fn main() {
    let totp: Totp = Builder::new()
        .with_secret(Secret::try_from_base32("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap())
        .build()
        .unwrap();

    let token = totp.generate_current();
    println!("{}", token);
}

is equivalent to:

use totp_rs::{Algorithm, Builder, Secret, Totp};

fn main() {
    let totp: Totp = Builder::new()
        .with_secret(Secret::try_from_base32("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap())
        .with_algorithm(Algorithm::SHA1)
        .with_digits(6)
        .with_skew(1)
        .with_step_duration(30)
        .build()
        .unwrap();

    let token = totp.generate_current();
    println!("{}", token);
}

You can override each property individually:

use totp_rs::{Builder, Secret, Totp};

fn main () {
    let totp: Totp = Builder::new()
        .with_secret(Secret::try_from_base32("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap())
        // optional, override a default; 8 digits is still RFC-compliant
        .with_digits(8)
        .build()
        .unwrap();
    let code = totp.generate_current();
    println!("code: {}", code);
}

You can ignore compliance checks by using Builder::build_noncompliant. Note that the checks are here for a reason, and unless you know what you're doing, you should really not have a lot of usecases for this one.

use totp_rs::{Builder, Secret, Totp};

fn main () {
    let totp: Totp = Builder::new()
        // Secret is too short to be compliant.
        .with_secret(Secret::try_from_base32("KRSXG5C").unwrap())
        // Cannot divide by zero.
        .with_step_duration(0)
        .build_noncompliant();

    // Panic here because you can't divide by zero.
    let code = totp.generate_current();
}

With gen_secret feature, you can go even further and have all values by default and a secure secret.

Note: With otpauth feature, the issuer defaults to None and the account name to "". Be sure to set them with Builder::with_issuer and Builder::with_account_name before generating an URL/QRCode.

fn main() {
    let totp = Totp::default();
    let code = totp.generate_current();
    println!("code: {}", code);
}

New TOTP from steam secret


Add it to your Cargo.toml:

[dependencies.totp-rs]
version = "^6.0"
features = ["steam"]

You can then do something like:

use totp_rs::{Builder, Secret, Totp};

fn main() {
    let totp: Totp = Builder::new_steam()
        .with_secret(Secret::try_from_base32("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap())
        .build()
        .unwrap();
    let code = totp.generate_current();
    println!("{}", code);
}

Using it without std


The crate is #![no_std] whenever the std feature is off, and needs no allocator at all when alloc is off too. serde, steam, gen_secret and zeroize all work in that configuration. otpauth, qr and migration do not, as they enable alloc.

[dependencies.totp-rs]
version = "^6.0"
default-features = false
# Anything except otpauth, qr and migration.
features = ["steam"]

Without alloc, a secret is exactly the 160 bits rfc-4226 recommends, built with Secret::new_stack:

use totp_rs::{Builder, Secret, Totp};

fn token_at(unix_time: u64) -> impl core::fmt::Display {
    let totp: Totp = Builder::new()
        .with_secret(Secret::new_stack(*b"12345678901234567890"))
        .build()
        .unwrap();

    // No system clock without `std`, so the timestamp is provided.
    totp.generate(unix_time)
}

With gen_secret, Secret::generate also works without an allocator. See examples/no_alloc.rs for a full bare-metal program; CI builds it for wasm32v1-none.