A pure Gleam implementation of RFC 6238 Time-Based One-Time Password (TOTP) algorithm, inspired by nimble_totp.
- 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
Add totp to your gleam.toml:
[dependencies]
totp = ">= 1.0.0"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=..."
}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)
)
}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 alternativecrypto.Sha512- Most secure option
Other algorithms in crypto.HashAlgorithm like crypto.Md5 are not supported for TOTP and will return UnsupportedHashAlgorithm error.
Possible errors:
pub type TotpError {
InvalidSecretLength
InvalidDigitsCount
InvalidTimeWindow
UnsupportedHashAlgorithm(crypto.HashAlgorithm)
CryptoError(String)
InvalidCode
}Generate a cryptographically secure random secret (20 bytes):
let secret = totp.secret()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"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 stringValidate 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") // -> FalseValidate a TOTP code with custom options:
let is_valid = totp.valid_with_options(secret, code, custom_options)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"
)Get default TOTP options:
let options = totp.default_options()
// TotpOptions(digits: 6, window: 30, hash_function: crypto.Sha1, tolerance: 1)// 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)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")
}
}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")
}
}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.)
}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")
}
}This library implements RFC 6238 TOTP with the following algorithm:
-
Time Step Calculation:
T = floor((current_time - T0) / X)T0= Unix epoch (0)X= time step (default: 30 seconds)
-
HMAC Generation:
HMAC = HMAC-SHA1(K, T)K= shared secret keyT= time step as 64-bit big-endian integer
-
Dynamic Truncation:
- Extract 4 bytes from HMAC starting at offset (last 4 bits of HMAC)
- Combine bytes into 31-bit integer (MSB cleared)
-
Code Generation:
Code = Truncated % 10^Digits
This library is compatible with:
- ✅ Google Authenticator
- ✅ Authy
- ✅ 1Password
- ✅ Bitwarden
- ✅ Any RFC 6238 compliant authenticator
Run the test suite:
gleam testThe tests cover:
- Secret generation
- Code generation and validation
- Custom options
- Error handling
- URI generation
- Hash function variations
- Edge cases
- Secret Storage: Store secrets securely, preferably encrypted at rest
- Time Synchronization: Ensure server clocks are synchronized (NTP)
- Rate Limiting: Implement rate limiting for TOTP validation attempts
- Secure Transmission: Always transmit codes over HTTPS
- Secret Generation: Use cryptographically secure random number generation
- Backup Codes: Provide backup recovery codes for account recovery
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Contributions are welcome! This is my first Gleam project so please, feel free to provide feedback or submit a Pull Request.