A TOTP-based time-varying code generator with alphanumeric and special character output
Project description
BetterTOTP
A TOTP-based time-varying code generator with alphanumeric and special character output. Uses HMAC-SHA512 (or SHA1/SHA256) to produce 12-character codes (configurable length) from a 75-character alphabet (A-Z a-z 0-9 !@#$%&*-_+=?~), with an encrypted vault for storing secrets. A native Android app is available in android/, and a standalone Linux binary in linux/.
Features
- Configurable TOTP — Choose hash algorithm (SHA1, SHA256, SHA512), code length, and time step
- Encrypted vault — Store multiple named secrets in
~/.config/btotp/vault.jsonencrypted with AES-256-GCM (PBKDF2-derived key from master password) - otpauth URI support — Import accounts from standard
otpauth://totp/...URIs (compatible with Bitwarden, Authy, Google Authenticator, etc.) - QR code display — Render QR codes directly in the terminal for mobile enrollment
- Clipboard copy — Copy codes to clipboard (supports pyperclip, xclip, xsel, pbcopy)
- Shell completions — Generate tab-completion scripts for bash, zsh, and fish
- Export/Import vault — JSON-based backup and restore
- User config — Persistent defaults via
~/.config/btotp/config.json - Android app — Native Kotlin app with Master Password or Biometric vault modes, Material 3 UI
- Linux binary — Standalone executable (no Python needed), Tkinter GUI if available, CLI fallback, registers as
otpauth://handler - Web API — FastAPI microservice for TOTP enrollment and verification, stateless, ready to integrate into any website backend
Installation
pip install bettertotp
# Optional feature groups:
pip install bettertotp[clipboard] # clipboard support (pyperclip)
pip install bettertotp[qr] # QR code display
pip install bettertotp[completion] # shell tab-completion
pip install bettertotp[all] # all optional features
From source
git clone https://github.com/skjaimini9/bettertotp.git
cd bettertotp
pip install .
Android app
Build the native Android APK from the android/ directory:
# 1. Generate a signing keystore (one-time)
keytool -genkey -v -keystore ~/.android/bettertotp-release.jks \
-alias bettertotp -keyalg RSA -keysize 2048 -validity 10000
# 2. Install Android SDK and set ANDROID_HOME
# 3. Download the Gradle wrapper
cd android
gradle wrapper
# 4. Build the release APK
BTOTP_KEYSTORE_PATH=~/.android/bettertotp-release.jks \
BTOTP_STORE_PASSWORD=your-keystore-password \
BTOTP_KEY_ALIAS=bettertotp \
BTOTP_KEY_PASSWORD=your-key-password \
./gradlew assembleRelease
# 5. Locate the signed APK
ls app/build/outputs/apk/release/app-release.apk
# 6. GPG-sign the APK for distribution
gpg --detach-sign --armor app/build/outputs/apk/release/app-release.apk
The Android app is a native Kotlin + Jetpack Compose application with two vault modes:
- Master Password — PBKDF2 + AES-256-GCM (identical algorithm to the CLI version)
- Biometric — Android Keystore-backed, unlocks with fingerprint or device PIN
Linux binary
Build the standalone executable from the linux/ directory:
# 1. Generate a GPG signing key (one-time, if signing releases)
gpg --full-generate-key
# 2. Install dependencies
pip install pyinstaller cryptography
# 3. Build the binary and release archive
cd linux
chmod +x build.sh
./build.sh
# 4. Locate the outputs
ls dist/btotp # single-file binary
ls dist/btotp-linux-x86_64.tar.gz # release archive
The binary works on any Linux distro with glibc ≥ 2.28 (Debian 10+, Ubuntu 18.04+, Fedora 30+, Arch). It adapts to your environment:
- With a display → Tkinter GUI window (unlock → live code list → add/copy/delete)
- Without a display → Falls back to the CLI (same as
btotpfrom pip) btotp otpauth://totp/...→ Imports the URI directly (GUI or CLI)- Register as URI handler →
./register-handler.shenables one-click import from a browser
Web backend API
A stateless FastAPI microservice for integrating BetterTOTP into any website backend.
# 1. Install dependencies
cd web
pip install -r requirements.txt
# 2. Start the server
uvicorn main:app --host 0.0.0.0 --port 8000
# 3. Enroll a new user
curl -X POST http://localhost:8000/api/enroll \
-H "Content-Type: application/json" \
-d '{"account": "user@example.com", "issuer": "MyApp"}'
# 4. Verify a TOTP code at login
curl -X POST http://localhost:8000/api/verify \
-H "Content-Type: application/json" \
-d '{"secret_hex": "<from step 3>", "code": "<user typed>"}'
Integration flow:
- User enables 2FA →
POST /api/enroll→ storesecret_hexin your DB → render QR fromuri - User logs in →
POST /api/verifywith the storedsecret_hex→ ifvalid: true, allow access
The API is stateless — your backend owns the secret storage. See web/README.md for full documentation.
Quick Start
1. Create a vault
btotp init
You'll be prompted for a master password. This creates ~/.config/btotp/vault.json.
2. Add an account
# Generate a random secret:
btotp add my-account --generate
# Or provide an existing hex secret:
btotp add my-account -s "a1b2c3d4..."
# Or import from an otpauth:// URI:
btotp import-uri "otpauth://totp/Example:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
# Or provide a base32 secret:
btotp add my-account --b32 "JBSWY3DPEHPK3PXP"
3. Get a code
btotp code -a my-account
4. List all accounts
btotp list
Output:
my-account 3vMU-DiSZn0n (12c / 45s)
github W078kA**ytRu (12c / 45s)
CLI Reference
btotp generate-secret
Generate a new random secret key (64 bytes by default).
btotp generate-secret [-o FILE] [-f {hex,b32}]
| Flag | Description |
|---|---|
-o, --output |
Write secret to file instead of stdout |
-f, --format |
Output format: hex (default, chunked) or b32 |
btotp code
Generate a TOTP code from a secret or vault account.
btotp code [-s SECRET | -a ACCOUNT] [-w] [--copy] [--algo ALGO] [--length N] [--step N]
| Flag | Description |
|---|---|
-s, --secret |
Secret key as a hex string |
-a, --account |
Account name in the vault |
-w, --watch |
Watch mode — refresh periodically (press Ctrl+C to stop) |
--copy |
Copy the code to clipboard instead of stdout |
--algo, --algorithm |
Hash algorithm: sha1, sha256, sha512 (default: sha512) |
--length |
Code length in characters (default: 12) |
--step |
Time step in seconds (default: 45) |
btotp verify
Verify a TOTP code against a secret or vault account.
btotp verify (-s SECRET | -a ACCOUNT) -c CODE [-n WINDOW] [--algo ALGO] [--length N] [--step N]
| Flag | Description |
|---|---|
-s, --secret |
Secret key as a hex string |
-a, --account |
Account name in the vault |
-c, --code |
Code to verify |
-n, --window |
Time drift window (default: 1 step in each direction) |
--algo, --algorithm |
Hash algorithm |
--length |
Code length |
--step |
Time step |
Exits with status 0 if valid, 1 if invalid.
btotp init
Create a new encrypted vault.
btotp init
Prompts for a master password (and confirmation). Creates ~/.config/btotp/vault.json.
btotp add
Add an account to the vault.
btotp add NAME [-s SECRET] [--b32 B32] [-u URI] [-g] [-i ISSUER] [--algo ALGO] [--digits N] [--period N]
| Argument | Description |
|---|---|
NAME |
Account name (required) |
-s, --secret |
Secret key as a hex string |
--b32 |
Secret key as a base32 string |
-u, --uri |
Import from an otpauth:// URI |
-g, --generate |
Generate a new random secret |
-i, --issuer |
Issuer name (e.g. "GitHub", "Google") |
--algo, --algorithm |
Hash algorithm (default: sha512) |
--digits |
Code length (default: 12) |
--period |
Time step in seconds (default: 45) |
btotp list
List all accounts in the vault with their current TOTP codes.
btotp list
btotp show
Show the current TOTP code for a specific account.
btotp show NAME [--copy]
| Flag | Description |
|---|---|
NAME |
Account name |
--copy |
Copy the code to clipboard |
btotp remove
Remove an account from the vault.
btotp remove NAME
btotp rename
Rename an account in the vault.
btotp rename OLD_NAME NEW_NAME
btotp import-uri
Import an account from an otpauth://totp/... URI.
btotp import-uri URI [-n NAME]
| Argument | Description |
|---|---|
URI |
The full otpauth://totp/... URI |
-n, --name |
Account name (defaults to the label from the URI) |
btotp export
Export the vault as plaintext JSON (secrets are visible — handle with care).
btotp export [-o FILE]
| Flag | Description |
|---|---|
-o, --output |
Write to file instead of stdout |
btotp import
Import accounts from a JSON file or stdin.
btotp import [FILE]
| Argument | Description |
|---|---|
FILE |
JSON file to import (reads from stdin if omitted) |
btotp uri
Generate an otpauth://totp/... URI for a vault account (for mobile enrollment).
btotp uri NAME [--qr]
| Flag | Description |
|---|---|
NAME |
Account name |
--qr |
Display the URI as a QR code in the terminal |
btotp completion
Generate shell completion script.
btotp completion {bash,zsh,fish}
Usage:
# Bash
source <(btotp completion bash)
# Zsh
autoload -U bashcompinit && bashcompinit && source <(btotp completion zsh)
# Fish
btotp completion fish | source
Requires argcomplete (pip install bettertotp[completion]).
btotp config
View or modify persistent configuration.
btotp config [--show] [--set KEY=VALUE]
| Flag | Description |
|---|---|
--show |
Display current configuration |
--set |
Set a configuration value |
Available keys:
| Key | Type | Default | Description |
|---|---|---|---|
time_step |
int | 45 | Default time step in seconds |
code_length |
int | 12 | Default code length in characters |
hash_algo |
string | "sha512" |
Default hash algorithm |
clipboard |
bool | false |
Always copy codes to clipboard |
Example:
btotp config --set time_step=30
btotp config --set code_length=8
btotp config --show
btotp help
Show help for any command.
btotp help [COMMAND]
Examples:
btotp help
btotp help code
btotp help list
Python API
generate_code(key_bytes, t=None, algorithm="sha512", code_length=12, time_step=45)
Generate a TOTP code.
from btotp import generate_code
code = generate_code(b"your-secret-key")
code = generate_code(b"secret", algorithm="sha256", code_length=8, time_step=30)
verify_code(key_bytes, code, window=1, algorithm="sha512", code_length=12, time_step=45)
Verify a TOTP code within a time window.
from btotp import verify_code
valid = verify_code(b"secret", code)
valid = verify_code(b"secret", code, algorithm="sha256", code_length=8)
generate_code_at(key_bytes, timestamp, ...)
Generate a code for a specific Unix timestamp.
from btotp import generate_code_at
code = generate_code_at(b"secret", 1000000, time_step=30)
generate_secret(length=64)
Generate a cryptographically random secret.
from btotp import generate_secret
secret = generate_secret() # 64 random bytes
secret = generate_secret(32) # 32 random bytes
Vault
Encrypted vault for storing multiple secrets.
from btotp import Vault
vault = Vault()
vault.unlock("master-password")
vault.add("email", secret.hex(), issuer="Google")
code = vault.code("email")
accounts = vault.list_accounts()
vault.remove("email")
Methods:
| Method | Description |
|---|---|
exists() |
Check if vault file exists |
create(password) |
Create a new vault |
unlock(password) |
Unlock an existing vault |
add(name, secret, ...) |
Add an account |
remove(name) |
Remove an account |
rename(old, new) |
Rename an account |
get(name) |
Get account details |
list_accounts() |
List all accounts |
code(name) |
Generate current code for account |
export_json() |
Export vault as JSON string |
import_json(data) |
Import accounts from JSON string |
parse_otpauth(uri)
Parse an otpauth://totp/... URI into its components.
from btotp import parse_otpauth
result = parse_otpauth("otpauth://totp/Example:user@example.com?secret=JBSWY3DPEHPK3PXP")
# {
# "secret": "...", # hex-encoded secret
# "issuer": "Example",
# "account": "user@example.com",
# "algorithm": "sha1",
# "digits": 12,
# "period": 45,
# }
generate_uri(account, secret_hex, issuer="", ...)
Generate an otpauth://totp/... URI.
from btotp import generate_uri
uri = generate_uri("myaccount", secret.hex(), issuer="MyApp",
algorithm="sha256", digits=8, period=30)
Configuration
Vault file
Encrypted vault at ~/.config/btotp/vault.json. The file contains:
{
"version": 1,
"salt": "<base64>",
"nonce": "<base64>",
"data": "<AES-256-GCM encrypted, base64-encoded JSON blob>"
}
- Encryption: AES-256-GCM with random 12-byte nonce
- Key derivation: PBKDF2-HMAC-SHA256, 600,000 iterations, random 16-byte salt
- Plaintext format (when decrypted):
{
"accounts": {
"my-account": {
"secret": "a1b2c3d4...",
"issuer": "Example",
"algorithm": "sha512",
"digits": 12,
"period": 45
}
}
}
User config
Persistent defaults at ~/.config/btotp/config.json:
{
"time_step": 30,
"code_length": 8,
"hash_algo": "sha256",
"clipboard": true
}
Security Considerations
- The vault master password is the single point of failure. Choose a strong password.
btotp exportoutputs plaintext JSON including all secrets. Handle exports securely.- The vault file itself is encrypted with AES-256-GCM, but the encryption strength depends on your master password.
- PBKDF2 iterations are set to 600,000 (OWASP 2023 recommendation for SHA-256).
- Secret keys default to 64 bytes (512 bits) of random data via
secrets.token_bytes().
Differences from Standard TOTP (RFC 6238)
| Aspect | Standard TOTP | BetterTOTP |
|---|---|---|
| Output | 6–8 decimal digits | 12 alphanumeric+special characters |
| Alphabet | 0-9 (10 chars) |
A-Z a-z 0-9 !@#$%&*-_+=?~ (75 chars) |
| Default hash | SHA-1 | SHA-512 |
| Default time step | 30 seconds | 45 seconds |
| Code generation | Dynamic truncation + modulo 10^N | Byte encoding into 75-char alphabet |
| Secret size | 10–20 bytes (80–160 bits) recommended | 64 bytes (512 bits) default |
Changelog
v1.0.0
- Android app — Native Kotlin + Jetpack Compose APK in
android/ - Dual vault modes: Master Password (PBKDF2 + AES-256-GCM) or Biometric (Android Keystore)
- TOTP core reimplemented in pure Kotlin (HMAC-SHA1/256/512, 75-char charset)
- Material 3 UI with dynamic color, navigation, and auto-refreshing codes
- Linux binary — PyInstaller-based standalone executable in
linux/ - Tkinter GUI with unlock, live code list, add/copy/delete
- Automatic GUI/CLI fallback based on display availability
otpauth://URI handler registration via xdg-mime- Web API — FastAPI microservice for website backend integration in
web/ - Stateless enrollment and verification endpoints (non-standard BetterTOTP algorithm)
- CORS configurable, ready for any web framework
- FOSS — MIT License. Author: Shri Kant
v0.2.0
- Configurable hash algorithm (
sha1,sha256,sha512) - Configurable code length and time step
- Encrypted vault (
btotp init,add,list,show,remove,rename) otpauth://totp/...URI import (btotp import-uri)- URI generation with optional QR display (
btotp uri --qr) - Clipboard copy support (
btotp code --copy,btotp show --copy) - User configuration (
btotp config) - Vault export/import (
btotp export,btotp import) - Shell completion scripts (
btotp completion) btotp helpsubcommand
v0.1.0
- Initial release
btotp generate-secret,btotp code,btotp verify- HMAC-SHA512 with 12-char codes, 45-second time step
- Watch mode (
--watch)
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bettertotp-0.2.0.tar.gz.
File metadata
- Download URL: bettertotp-0.2.0.tar.gz
- Upload date:
- Size: 20.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de76a8b50bc3671f47b629a73c165ec4d9b984338bd18501449a6b2ed7f296f8
|
|
| MD5 |
77aaf0aa1273790f70cdc0f03e2909f2
|
|
| BLAKE2b-256 |
e5c55e60177323c03c1246f0ad7088641677c2f652b12f53a108f8709ba0a0b6
|
File details
Details for the file bettertotp-0.2.0-py3-none-any.whl.
File metadata
- Download URL: bettertotp-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af28ab1092b345b2ab89f4dcde9e4432912f6dafe7413e06e4143049db6964b8
|
|
| MD5 |
9a04da9b2129cee939c7cdd5e9e779c5
|
|
| BLAKE2b-256 |
67252948b57ae7845f6e207a1a75f2e7ffd975236218c37e3d4fd7927f641efa
|