Expand description

OTP implementations based on RFC 4226 for Hmac-based OTPs and RFC 6238 for Time-based OTPs.

By default all features are enabled, if you wish only to use the default functionality (no encoding, custom or qr modules), use the flag default-features = false

This module by itself allows you to generate and verify TOTPs and HOTPs using the default algorithm SHA-1, the default digit length of 6 and the default time step of 30 for TOTPs.

The following applies only if you set default-features = false, by default it is included:

If you need finer controls over password generation and verification use the custom_otp feature flag to gain access to the custom module.

The encoding feature flag gives access to the encoding module which provides 2 basic functions to encode and decode the generated keys to an encoding of choice avilable from the data_encoding crate.

The qr feature flag gives access to the qr module and enables QR code generation of the generated secret keys ready to be used by authenticator apps.

Example usage

Generate a secret and qr code, and verify a password generated with said secret:

use thotp::{
    otp,
    verify_totp,
    generate_secret,
    encoding::{encode, decode},
    qr,
};
use std::time::{SystemTime, UNIX_EPOCH};

// The default time step used by this module internally
const TIME_STEP: u8 = 30;

// Generate an encoded secret

let secret = generate_secret(80);

// The data_encoding crate is re-exported for convenience
let encoded = encode(&secret, data_encoding::BASE32);
  
// ...store the secret somewhere safe...

let uri = qr::otp_uri(
    // Type of otp
    "totp",
    // The encoded secret
    &encoded,
    // Your big corp title
    "Big Corp:john.doe@email.com",
    // Your big corp issuer
    "Big Corp",
    // We are generating a TOTP so we don't need a counter value
    None,
).expect("yikes");

let qr_code = qr::generate_code_svg(
    &uri,
    // The qr code width (None defaults to 200)
    None,
    // The qr code height (None defaults to 200)
    None,
    // Correction level, M is the default
    qrcode::EcLevel::M,
)
.expect("uh oh");

// ..scan the qr code with an authenticator app...

// Verify a password provided from the client

// When generating an OTP we have to calculate the current time slice. This is necessary
// upfront only when generating an otp since this function is blind to the OTP type.
let time_step_now = SystemTime::now()
     .duration_since(UNIX_EPOCH)
     .unwrap()
     .as_secs()
     / TIME_STEP as u64;

// Let us assume this comes from the client
let pw = otp(&secret, time_step_now).unwrap();

// The verify function calculates the current slice internally
let (result, discrepancy) = verify_totp(&pw, &secret, 0).unwrap();

assert_eq!((true, 0),(result, discrepancy));

A way to quickly test your QR with an authenticator

The following are copy pasteable functions for rapid testing with authenticator apps

Use the following function to generate and encode a secret and create a qr code. Uncomment the 2 write lines to write the secret to a file called temp_secret and the qr code string to the file qr.html.

fn generate_code() {
    let secret = thotp::generate_secret(80);
    let secret = &thotp::encoding::encode(&secret, data_encoding::BASE32);
     
    let uri = thotp::qr::otp_uri("totp", &secret, "THOTP:test@email.com", "THOTP", None).unwrap();

    let code = thotp::qr::generate_code_svg(
        &uri,
        Some(300),
        Some(300),
        thotp::qr::EcLevel::H,
    )
    .unwrap();

    // Uncomment these lines to write the temporary files

    // std::fs::write("./temp_secret", secret).unwrap();
    // std::fs::write("./qr.html", code).unwrap();
}

Load the html file in your browser and scan it with an authenticator app. The temp_secret file is used to temporarily hold the secret for the generated qr code. Once you’ve loaded the code to the app, you can use the following function to print out an TOTP generated with the default parameters (SHA1, 6 digits, Time step = 30).

fn print_pw_totp(secret: &str /*use the string from the temp_secret file */) {
    let secret = decode(secret, data_encoding::BASE32).unwrap();
    let nonce = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs()
        / TIME_STEP as u64;
    let totp = otp(&secret, nonce).unwrap();
    println!("TOTP: {}", totp);
}

To test HOTPs, simply replace the “totp” parameter with “hotp” and use the following function:

fn print_pw_hotp(secret: &str /*use the string from the temp_secret file */, counter: u64) -> Result<(), ThotpError> {
    let secret = decode(secret, data_encoding::BASE32).unwrap();
    let hotp = otp(&secret, counter).unwrap();
    println!("HOTP: {}", hotp);
}

There are 3 constants used by this module internally;

The DIGITS_DEFAULT constant is the default password length generated by the otp function as well as when verifying and is equal to 6.

The TIME_STEP is the default and RFC recommended time step used to divide the duration in seconds from now until the unix epoch and is equal to 30.

When TOTPs are generated, there is a chance they will be generated at the end of a time step and by the time they reach the server the password would be invalid because it would fall in the previous time step. This is mitigated by allowing passwords from ALLOWED_DRIFT time steps prior and subsequent to the current to be valid. The value of this is the RFC recommended amount 1, meaning the passwords from the time slice prior and subsequent to the current one are considered valid.

The same drift can happen with HOTPs with the counter, and a lookahead parameter can be used to adjust how many passwords will be considered valid from the current counter.

A note on key length

The GA wiki says that a secret of 80 bits is required, however keys with longer buffer sizes (160 specifically) were succesfully registered and were giving correct passwords, so there is a chance the wikipedia page is deprecated. The RFC recommended key length is 160 so it is advised you stick to 160 for the secret length for the recommended security if it works. The length refers to the buffer size for the generate_secret function, NOT the Base32 encoded version of it.

Modules

Contains functions providing finer control over OTP generation and verification parameters. This module re-exports the hashing algorithms Sha1, Sha256 and Sha512 to use with the provided functions.
A simple module containing 2 functions to encode and decode the secrets generated by this crate’s generate_secret function to and from the given encoding available in the data_encoding crate.
Contains a function to generate a qr code ready to be scanned by an authenticator app. Tested and working on Google Authenticator and FreeOTP. OTP uris generated by this module (which are encoded in the QR itself) are done so using this specification.

Enums

A wrapper around all the possible errors that can be encountered when using this module. When generating OTPs an error may occur if an invalid length is provided to the Hmac hasher as well as when calculating the system time so we have to take it in to account and handle it properly. Additional errors are covered when using the custom, qr or decoding modules.

Functions

Generates a secret key, i.e. a buffer filled with random bytes. The RFC recommended buffer size is 160.
Uses HMAC-SHA-1 and the default digit length of 6 to generate a one time password.
Generates multiple hotp passwords in the range of lookahead + 1 and compares them to the input. The counter wraps around on overflow. A lookahead of 0 means only the current counter will be used in the verification.
Verifies the given password for the given timestamp and secret.