totp-rs

repository·master·Indexed 42 days ago

https://github.com/constantoine/totp-rs

An RFC-compliant Time-based One-Time Password (TOTP) implementation for Rust. Version 6.0.0 provides a Builder API for creating and verifying tokens, supporting configurable algorithms (SHA1, SHA256, SHA512), time skews, and digit counts. It includes optional features for QR code generation, otpauth URL parsing, random secret generation, and specialized Steam TOTP support. The library is #![no_std] compatible.

Tokens
9.5K
Snippets
38
Records
45
Agent score
50%

What's inside totp-rs

  1. Understand the Secret type

    master

    Secret is an opaque type that holds the raw bytes of the TOTP secret. You can initialize it from raw bytes or from a Base32-encoded string. Both methods result in the same underlying value.

    • Use Secret::from(bytes) for raw byte slices.
    • Use Secret::try_from_base32(string) for Base32 encoded strings.
    // From raw bytes
    Secret::from("TestSecretSuperSecret".as_bytes())
    
    // From Base32
    Secret::try_from_base32("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap()
  2. Steam TOTP support (requires 'steam' feature)

    master

    To support Steam-specific TOTP tokens, enable the steam feature.

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

    Use Builder::new_steam() instead of Builder::new() to initialize the builder with Steam's specific requirements.

    use totp_rs::{Builder, Secret, Totp};
    
    let totp = Builder::new_steam()
        .with_secret(Secret::try_from_base32("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap())
        .build()
        .unwrap();
    
    let code = totp.generate_current();
  3. Parse and use otpauth URLs (requires 'otpauth' feature)

    master

    With the otpauth feature enabled, you can parse standard otpauth:// URLs directly into a Totp object.

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

    Use Totp::from_url(url_string) to instantiate the object.

    use totp_rs::Totp;
    
    let otpauth = "otpauth://totp/GitHub:constantoine@github.com?secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&issuer=GitHub";
    let totp = Totp::from_url(otpauth).unwrap();
    println!("{}", totp.generate_current());
  4. Use totp-rs in no_std environments

    master

    totp-rs is #![no_std]. If you disable the std feature, you lose access to methods that rely on the system clock (like generate_current, check_current, ttl, and next_step_current).

    In a no_std environment, you must provide the Unix timestamp manually to the generate, check, and next_step methods.

    Configuration for no_std

    To minimize dependencies, disable default-features. Note that otpauth, qr, and migration require alloc and cannot be used in a no_alloc environment.

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

    Example: no_std with manual timestamp

    use totp_rs::{Builder, Secret, Totp};
    
    fn generate_manual(unix_time: u64) -> String {
        let totp: Totp = Builder::new()
            .with_secret(Secret::new_stack(*b"12345678901234567890"))
            .build()
            .unwrap();
    
        // Use generate(timestamp) instead of generate_current()
        totp.generate(unix_time).to_string()
    }
    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();
    
        totp.generate(unix_time)
    }
  5. Generate random secrets (requires 'gen_secret' feature)

    master

    The gen_secret feature allows you to generate a new secret automatically during the build process. This is useful for creating new 2FA accounts.

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

    When this feature is enabled, Builder::new() automatically includes a freshly generated secret, so you can call .build() immediately. Alternatively, you can call Secret::generate() explicitly.

    use totp_rs::{Builder, Totp};
    
    // Builder::new() already contains a generated secret
    let totp = Builder::new().build().unwrap();
    
    // Or explicitly
    use totp_rs::Secret;
    let secret = Secret::generate();
  6. Migrate from totp-rs 5.7.x to 6.0.0

    master

    Version 6.0.0 introduces breaking changes to the public API, including a new Builder pattern for construction, a unified TotpError type, and changes to how Secret and Token are handled.

    To facilitate an incremental migration, the migration feature (enabled by default) provides deprecated aliases and shims. Once your code is fully updated, you can disable the migration feature by setting default-features = false and re-enabling only the features you use to turn remaining 5.x calls into hard errors.

    // In Cargo.toml, to disable migration shims after porting:
    [dependencies]
    totp-rs = { version = "6.0.0", default-features = false, features = ["serde", "otpauth"] }
  7. Generate QR codes (requires 'qr' feature)

    master

    To generate a Base64-encoded PNG QR code, enable the qr feature in your Cargo.toml. This feature also enables the otpauth feature.

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

    Use the to_qr_base64() method on a Totp instance. Note that for QR codes, you should typically set an issuer and account name via the builder.

    use totp_rs::{Builder, Secret, Totp};
    
    let 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);
  8. How the Builder handles secret generation

    master

    The Builder has built-in support for automatic secret generation if the gen_secret feature is enabled.

    1. Eager vs Lazy: Calling Builder::new() does not generate a secret immediately. The secret is only generated when .build() or .build_noncompliant() is called if no secret has been manually provided via .with_secret().
    2. Security: When gen_secret is enabled, the builder uses a strong source to generate a random 160-bit secret.
    3. Fallback: If gen_secret is not enabled, you must call .with_secret() before calling .build(), otherwise it will return TotpError::SecretNotSet.
  9. Manage TOTP shared secrets with the `Secret` type

    master

    The Secret struct represents the shared secret used for TOTP generation and validation. It can store secrets as raw bytes or handle Base32 encoded strings. Depending on the size and enabled features, secrets are stored either on the stack (for exactly 20 bytes) or on the heap (if the alloc feature is enabled).

    Security Note: The Debug implementation is redacted to prevent accidental leakage in logs. Use as_bytes(), to_base32(), or Display (which outputs hex) to access the actual secret data.

    use totp_rs::Secret;
    
    // Example: Creating a secret from a Base32 string
    let secret = Secret::try_from_base32("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG").unwrap();
    
    // Example: Accessing raw bytes
    let bytes = secret.as_bytes();
  10. Configure feature flags and no_std support

    master
    • std: Enabled by default. For no_std environments, set default-features = false.
    • serde: Renamed from serde_support. Enables serialization/deserialization.
    • otpauth: Required for generating OTPAuth URLs.
    • gen_secret: Required for Secret::generate().
    • zeroize: Enables zeroizing Secret on drop.

    Warning on Serialized Data: The serde representation changed in 6.0. Data persisted with 5.7.x is not guaranteed to deserialize under 6.0. You should re-issue or re-serialize stored values. Specifically, digits changed from usize to u32 and skew from u8 to u16, which may break width-sensitive binary formats like bincode.

  11. Understand the `Token` type

    master

    The Token struct represents a TOTP token generated by Totp. It is designed with security and efficiency in mind:

    • Stack Allocated: It is only stack-allocated.
    • Constant-Time Comparison: Comparison is performed in constant time regardless of the number of digits, protecting against timing attacks.
    • Lazy Formatting: String formatting is evaluated lazily.
    • Security: If the zeroize feature is enabled, Token implements Zeroize and ZeroizeOnDrop to securely clear the token from memory when it goes out of scope.

    Important Note on Comparison: While Token implements Eq, it is strongly recommended to use Totp::check instead. check accounts for clock skew, whereas Eq performs a strict equality check.

    Note on Copying: Token does not implement Copy to ensure compatibility with the zeroize feature's Drop implementation.

    # use totp_rs::Token;
    # extern crate alloc;
    # use alloc::{format, string::String};
    # #[cfg(feature = "alloc")]
    # fn _foo(token: Token) {
    let text: String = format!("{}", token);
    # }