Skip to content

xikaos/totp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TOTP

A pure Gleam implementation of RFC 6238 Time-Based One-Time Password (TOTP) algorithm, inspired by nimble_totp.

Features

  • RFC 6238 Compliant: Fully implements the TOTP standard
  • Multiple Hash Functions: Support for SHA1, SHA256, and SHA512
  • Configurable Options: Customizable digits, time windows, and tolerance
  • QR Code URI Generation: Generate otpauth:// URIs for QR codes

Installation

Add totp to your gleam.toml:

[dependencies]
totp = ">= 1.0.0"

Quick Start

import totp

pub fn main() {
  // Generate a random secret
  let secret = totp.secret()
  
  // Generate a TOTP code
  let assert Ok(code) = totp.verification_code(secret)
  
  // Validate the code
  let is_valid = totp.valid(secret, code)
  // -> True
  
  // Generate QR code URI
  let uri = totp.otpauth_uri("user@example.com", secret)
  // -> "otpauth://totp/user@example.com?secret=..."
}

API Reference

Types

TotpOptions

Configuration options for TOTP generation and validation:

pub type TotpOptions {
  TotpOptions(
    digits: Int,          // Number of digits (4-10, default: 6)
    window: Int,          // Time window in seconds (default: 30)
    hash_function: crypto.HashAlgorithm,  // Hash algorithm (default: crypto.Sha1)
    tolerance: Int,       // Windows to check (default: 1)
  )
}

Hash Algorithms

This library uses crypto.HashAlgorithm from the gleam_crypto package. Supported hash algorithms for TOTP:

  • crypto.Sha1 - Most commonly used (default)
  • crypto.Sha256 - More secure alternative
  • crypto.Sha512 - Most secure option

Other algorithms in crypto.HashAlgorithm like crypto.Md5 are not supported for TOTP and will return UnsupportedHashAlgorithm error.

TotpError

Possible errors:

pub type TotpError {
  InvalidSecretLength
  InvalidDigitsCount
  InvalidTimeWindow
  UnsupportedHashAlgorithm(crypto.HashAlgorithm)
  CryptoError(String)
  InvalidCode
}

Core Functions

secret() -> BitArray

Generate a cryptographically secure random secret (20 bytes):

let secret = totp.secret()

verification_code(secret: BitArray) -> Result(String, TotpError)

Generate a TOTP code using default options:

let secret = totp.secret()
let assert Ok(code) = totp.verification_code(secret)
// code is a 6-digit string like "123456"

verification_code_with_options(secret: BitArray, options: TotpOptions) -> Result(String, TotpError)

Generate a TOTP code with custom options:

import gleam/crypto

let secret = totp.secret()
let custom_options = totp.TotpOptions(
  digits: 8,
  window: 60,
  hash_function: crypto.Sha256,
  tolerance: 2
)

// Or use convenience functions:
let custom_options = totp.options_sha256(8, 60, 2)

let assert Ok(code) = totp.verification_code_with_options(secret, custom_options)
// code is an 8-digit string

valid(secret: BitArray, code: String) -> Bool

Validate a TOTP code using default options:

let secret = totp.secret()
let assert Ok(code) = totp.verification_code(secret)

totp.valid(secret, code)  // -> True
totp.valid(secret, "000000")  // -> False

valid_with_options(secret: BitArray, code: String, options: TotpOptions) -> Bool

Validate a TOTP code with custom options:

let is_valid = totp.valid_with_options(secret, code, custom_options)

otpauth_uri(label: String, secret: BitArray) -> String

Generate an otpauth:// URI for QR code generation:

let secret = totp.secret()
let uri = totp.otpauth_uri("user@example.com", secret)
// -> "otpauth://totp/user@example.com?secret=ABCDEFGH...&digits=6&period=30&algorithm=SHA1"

otpauth_uri_with_options(label: String, secret: BitArray, options: TotpOptions, issuer: String) -> String

Generate an otpauth:// URI with custom options and issuer:

let uri = totp.otpauth_uri_with_options(
  "user@example.com", 
  secret, 
  custom_options, 
  "MyApp"
)

Utility Functions

default_options() -> TotpOptions

Get default TOTP options:

let options = totp.default_options()
// TotpOptions(digits: 6, window: 30, hash_function: crypto.Sha1, tolerance: 1)

Convenience Functions for Creating Options

// Create options with specific hash algorithms
let sha1_opts = totp.options_sha1(digits: 6, window: 30, tolerance: 1)
let sha256_opts = totp.options_sha256(digits: 8, window: 60, tolerance: 2)  
let sha512_opts = totp.options_sha512(digits: 4, window: 15, tolerance: 0)

Examples

Basic Usage

import totp

pub fn basic_example() {
  let secret = totp.secret()
  
  case totp.verification_code(secret) {
    Ok(code) -> {
      io.println("Generated code: " <> code)
      
      let is_valid = totp.valid(secret, code)
      io.println("Code is valid: " <> bool.to_string(is_valid))
    }
    Error(error) -> io.println("Error generating code")
  }
}

Custom Configuration

import totp

pub fn custom_example() {
  let secret = totp.secret()
  
  // Use 8 digits, 60-second window, SHA256
  let options = totp.TotpOptions(
    digits: 8,
    window: 60,
    hash_function: totp.Sha256,
    tolerance: 2  // Check ±2 windows for clock drift
  )
  
  case totp.verification_code_with_options(secret, options) {
    Ok(code) -> {
      io.println("8-digit code: " <> code)
      
      // Validate with same options
      let is_valid = totp.valid_with_options(secret, code, options)
      io.println("Valid: " <> bool.to_string(is_valid))
    }
    Error(_) -> io.println("Error")
  }
}

QR Code Integration

import totp

pub fn qr_code_example() {
  let secret = totp.secret()
  
  // Generate URI for QR code
  let uri = totp.otpauth_uri_with_options(
    "alice@example.com",
    secret,
    totp.default_options(),
    "MySecureApp"
  )
  
  io.println("QR Code URI: " <> uri)
  // Use this URI to generate a QR code that users can scan
  // with their authenticator apps (Google Authenticator, Authy, etc.)
}

Error Handling

import totp

pub fn error_handling_example() {
  let secret = totp.secret()
  
  let invalid_options = totp.TotpOptions(
    digits: 15,  // Invalid: too many digits
    window: 30,
    hash_function: totp.Sha1,
    tolerance: 1
  )
  
  case totp.verification_code_with_options(secret, invalid_options) {
    Ok(code) -> io.println("Code: " <> code)
    Error(totp.InvalidDigitsCount) -> io.println("Error: Invalid digits count")
    Error(totp.InvalidTimeWindow) -> io.println("Error: Invalid time window")
    Error(totp.CryptoError(msg)) -> io.println("Crypto error: " <> msg)
    Error(_) -> io.println("Other error")
  }
}

Algorithm Details

This library implements RFC 6238 TOTP with the following algorithm:

  1. Time Step Calculation: T = floor((current_time - T0) / X)

    • T0 = Unix epoch (0)
    • X = time step (default: 30 seconds)
  2. HMAC Generation: HMAC = HMAC-SHA1(K, T)

    • K = shared secret key
    • T = time step as 64-bit big-endian integer
  3. Dynamic Truncation:

    • Extract 4 bytes from HMAC starting at offset (last 4 bits of HMAC)
    • Combine bytes into 31-bit integer (MSB cleared)
  4. Code Generation: Code = Truncated % 10^Digits

Compatibility

This library is compatible with:

  • ✅ Google Authenticator
  • ✅ Authy
  • ✅ 1Password
  • ✅ Bitwarden
  • ✅ Any RFC 6238 compliant authenticator

Testing

Run the test suite:

gleam test

The tests cover:

  • Secret generation
  • Code generation and validation
  • Custom options
  • Error handling
  • URI generation
  • Hash function variations
  • Edge cases

Security Considerations

  1. Secret Storage: Store secrets securely, preferably encrypted at rest
  2. Time Synchronization: Ensure server clocks are synchronized (NTP)
  3. Rate Limiting: Implement rate limiting for TOTP validation attempts
  4. Secure Transmission: Always transmit codes over HTTPS
  5. Secret Generation: Use cryptographically secure random number generation
  6. Backup Codes: Provide backup recovery codes for account recovery

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Contributing

Contributions are welcome! This is my first Gleam project so please, feel free to provide feedback or submit a Pull Request.

About

Gleam TOTP generator/validator fully compliant with RFC 6238

Resources

Stars

Watchers

Forks

Packages

No packages published