bettertotp 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bettertotp-0.2.0/LICENSE +21 -0
- bettertotp-0.2.0/PKG-INFO +685 -0
- bettertotp-0.2.0/README.md +647 -0
- bettertotp-0.2.0/bettertotp.egg-info/PKG-INFO +685 -0
- bettertotp-0.2.0/bettertotp.egg-info/SOURCES.txt +25 -0
- bettertotp-0.2.0/bettertotp.egg-info/dependency_links.txt +1 -0
- bettertotp-0.2.0/bettertotp.egg-info/entry_points.txt +2 -0
- bettertotp-0.2.0/bettertotp.egg-info/requires.txt +15 -0
- bettertotp-0.2.0/bettertotp.egg-info/top_level.txt +1 -0
- bettertotp-0.2.0/btotp/__init__.py +14 -0
- bettertotp-0.2.0/btotp/__main__.py +4 -0
- bettertotp-0.2.0/btotp/charset.py +17 -0
- bettertotp-0.2.0/btotp/cli.py +422 -0
- bettertotp-0.2.0/btotp/clipboard.py +64 -0
- bettertotp-0.2.0/btotp/config.py +32 -0
- bettertotp-0.2.0/btotp/core.py +61 -0
- bettertotp-0.2.0/btotp/qr.py +14 -0
- bettertotp-0.2.0/btotp/secret.py +30 -0
- bettertotp-0.2.0/btotp/uri.py +86 -0
- bettertotp-0.2.0/btotp/vault.py +143 -0
- bettertotp-0.2.0/pyproject.toml +55 -0
- bettertotp-0.2.0/setup.cfg +4 -0
- bettertotp-0.2.0/tests/test_clipboard.py +13 -0
- bettertotp-0.2.0/tests/test_config.py +50 -0
- bettertotp-0.2.0/tests/test_core.py +274 -0
- bettertotp-0.2.0/tests/test_uri.py +64 -0
- bettertotp-0.2.0/tests/test_vault.py +156 -0
bettertotp-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Shri Kant
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bettertotp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A TOTP-based time-varying code generator with alphanumeric and special character output
|
|
5
|
+
Author-email: Shri Kant <skjaimini9@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/skjaimini9/bettertotp
|
|
8
|
+
Project-URL: Repository, https://github.com/skjaimini9/bettertotp
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/skjaimini9/bettertotp/issues
|
|
10
|
+
Keywords: totp,2fa,authentication,security,one-time-password,hmac,sha512
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Security :: Cryptography
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: cryptography
|
|
27
|
+
Provides-Extra: clipboard
|
|
28
|
+
Requires-Dist: pyperclip; extra == "clipboard"
|
|
29
|
+
Provides-Extra: qr
|
|
30
|
+
Requires-Dist: qrcode[pil]; extra == "qr"
|
|
31
|
+
Provides-Extra: completion
|
|
32
|
+
Requires-Dist: argcomplete; extra == "completion"
|
|
33
|
+
Provides-Extra: all
|
|
34
|
+
Requires-Dist: bettertotp[clipboard]; extra == "all"
|
|
35
|
+
Requires-Dist: bettertotp[qr]; extra == "all"
|
|
36
|
+
Requires-Dist: bettertotp[completion]; extra == "all"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# BetterTOTP
|
|
40
|
+
|
|
41
|
+
[](https://python.org)
|
|
42
|
+
[](LICENSE)
|
|
43
|
+
[](android/)
|
|
44
|
+
[](linux/)
|
|
45
|
+
[](web/)
|
|
46
|
+
|
|
47
|
+
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/`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
- **Configurable TOTP** — Choose hash algorithm (SHA1, SHA256, SHA512), code length, and time step
|
|
54
|
+
- **Encrypted vault** — Store multiple named secrets in `~/.config/btotp/vault.json` encrypted with AES-256-GCM (PBKDF2-derived key from master password)
|
|
55
|
+
- **otpauth URI support** — Import accounts from standard `otpauth://totp/...` URIs (compatible with Bitwarden, Authy, Google Authenticator, etc.)
|
|
56
|
+
- **QR code display** — Render QR codes directly in the terminal for mobile enrollment
|
|
57
|
+
- **Clipboard copy** — Copy codes to clipboard (supports pyperclip, xclip, xsel, pbcopy)
|
|
58
|
+
- **Shell completions** — Generate tab-completion scripts for bash, zsh, and fish
|
|
59
|
+
- **Export/Import vault** — JSON-based backup and restore
|
|
60
|
+
- **User config** — Persistent defaults via `~/.config/btotp/config.json`
|
|
61
|
+
- **Android app** — Native Kotlin app with Master Password or Biometric vault modes, Material 3 UI
|
|
62
|
+
- **Linux binary** — Standalone executable (no Python needed), Tkinter GUI if available, CLI fallback, registers as `otpauth://` handler
|
|
63
|
+
- **Web API** — FastAPI microservice for TOTP enrollment and verification, stateless, ready to integrate into any website backend
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install bettertotp
|
|
71
|
+
|
|
72
|
+
# Optional feature groups:
|
|
73
|
+
pip install bettertotp[clipboard] # clipboard support (pyperclip)
|
|
74
|
+
pip install bettertotp[qr] # QR code display
|
|
75
|
+
pip install bettertotp[completion] # shell tab-completion
|
|
76
|
+
pip install bettertotp[all] # all optional features
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### From source
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/skjaimini9/bettertotp.git
|
|
83
|
+
cd bettertotp
|
|
84
|
+
pip install .
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Android app
|
|
88
|
+
|
|
89
|
+
Build the native Android APK from the `android/` directory:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# 1. Generate a signing keystore (one-time)
|
|
93
|
+
keytool -genkey -v -keystore ~/.android/bettertotp-release.jks \
|
|
94
|
+
-alias bettertotp -keyalg RSA -keysize 2048 -validity 10000
|
|
95
|
+
|
|
96
|
+
# 2. Install Android SDK and set ANDROID_HOME
|
|
97
|
+
|
|
98
|
+
# 3. Download the Gradle wrapper
|
|
99
|
+
cd android
|
|
100
|
+
gradle wrapper
|
|
101
|
+
|
|
102
|
+
# 4. Build the release APK
|
|
103
|
+
BTOTP_KEYSTORE_PATH=~/.android/bettertotp-release.jks \
|
|
104
|
+
BTOTP_STORE_PASSWORD=your-keystore-password \
|
|
105
|
+
BTOTP_KEY_ALIAS=bettertotp \
|
|
106
|
+
BTOTP_KEY_PASSWORD=your-key-password \
|
|
107
|
+
./gradlew assembleRelease
|
|
108
|
+
|
|
109
|
+
# 5. Locate the signed APK
|
|
110
|
+
ls app/build/outputs/apk/release/app-release.apk
|
|
111
|
+
|
|
112
|
+
# 6. GPG-sign the APK for distribution
|
|
113
|
+
gpg --detach-sign --armor app/build/outputs/apk/release/app-release.apk
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The Android app is a native Kotlin + Jetpack Compose application with two vault modes:
|
|
117
|
+
- **Master Password** — PBKDF2 + AES-256-GCM (identical algorithm to the CLI version)
|
|
118
|
+
- **Biometric** — Android Keystore-backed, unlocks with fingerprint or device PIN
|
|
119
|
+
|
|
120
|
+
### Linux binary
|
|
121
|
+
|
|
122
|
+
Build the standalone executable from the `linux/` directory:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# 1. Generate a GPG signing key (one-time, if signing releases)
|
|
126
|
+
gpg --full-generate-key
|
|
127
|
+
|
|
128
|
+
# 2. Install dependencies
|
|
129
|
+
pip install pyinstaller cryptography
|
|
130
|
+
|
|
131
|
+
# 3. Build the binary and release archive
|
|
132
|
+
cd linux
|
|
133
|
+
chmod +x build.sh
|
|
134
|
+
./build.sh
|
|
135
|
+
|
|
136
|
+
# 4. Locate the outputs
|
|
137
|
+
ls dist/btotp # single-file binary
|
|
138
|
+
ls dist/btotp-linux-x86_64.tar.gz # release archive
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The binary works on any Linux distro with glibc ≥ 2.28 (Debian 10+, Ubuntu 18.04+, Fedora 30+, Arch). It adapts to your environment:
|
|
142
|
+
|
|
143
|
+
- **With a display** → Tkinter GUI window (unlock → live code list → add/copy/delete)
|
|
144
|
+
- **Without a display** → Falls back to the CLI (same as `btotp` from pip)
|
|
145
|
+
- **`btotp otpauth://totp/...`** → Imports the URI directly (GUI or CLI)
|
|
146
|
+
- **Register as URI handler** → `./register-handler.sh` enables one-click import from a browser
|
|
147
|
+
|
|
148
|
+
### Web backend API
|
|
149
|
+
|
|
150
|
+
A stateless FastAPI microservice for integrating BetterTOTP into any website backend.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# 1. Install dependencies
|
|
154
|
+
cd web
|
|
155
|
+
pip install -r requirements.txt
|
|
156
|
+
|
|
157
|
+
# 2. Start the server
|
|
158
|
+
uvicorn main:app --host 0.0.0.0 --port 8000
|
|
159
|
+
|
|
160
|
+
# 3. Enroll a new user
|
|
161
|
+
curl -X POST http://localhost:8000/api/enroll \
|
|
162
|
+
-H "Content-Type: application/json" \
|
|
163
|
+
-d '{"account": "user@example.com", "issuer": "MyApp"}'
|
|
164
|
+
|
|
165
|
+
# 4. Verify a TOTP code at login
|
|
166
|
+
curl -X POST http://localhost:8000/api/verify \
|
|
167
|
+
-H "Content-Type: application/json" \
|
|
168
|
+
-d '{"secret_hex": "<from step 3>", "code": "<user typed>"}'
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Integration flow:**
|
|
172
|
+
1. User enables 2FA → `POST /api/enroll` → store `secret_hex` in your DB → render QR from `uri`
|
|
173
|
+
2. User logs in → `POST /api/verify` with the stored `secret_hex` → if `valid: true`, allow access
|
|
174
|
+
|
|
175
|
+
The API is stateless — your backend owns the secret storage. See `web/README.md` for full documentation.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Quick Start
|
|
180
|
+
|
|
181
|
+
### 1. Create a vault
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
btotp init
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
You'll be prompted for a master password. This creates `~/.config/btotp/vault.json`.
|
|
188
|
+
|
|
189
|
+
### 2. Add an account
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Generate a random secret:
|
|
193
|
+
btotp add my-account --generate
|
|
194
|
+
|
|
195
|
+
# Or provide an existing hex secret:
|
|
196
|
+
btotp add my-account -s "a1b2c3d4..."
|
|
197
|
+
|
|
198
|
+
# Or import from an otpauth:// URI:
|
|
199
|
+
btotp import-uri "otpauth://totp/Example:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
|
|
200
|
+
|
|
201
|
+
# Or provide a base32 secret:
|
|
202
|
+
btotp add my-account --b32 "JBSWY3DPEHPK3PXP"
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 3. Get a code
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
btotp code -a my-account
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 4. List all accounts
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
btotp list
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Output:
|
|
218
|
+
```
|
|
219
|
+
my-account 3vMU-DiSZn0n (12c / 45s)
|
|
220
|
+
github W078kA**ytRu (12c / 45s)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## CLI Reference
|
|
226
|
+
|
|
227
|
+
### `btotp generate-secret`
|
|
228
|
+
|
|
229
|
+
Generate a new random secret key (64 bytes by default).
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
btotp generate-secret [-o FILE] [-f {hex,b32}]
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
| Flag | Description |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `-o, --output` | Write secret to file instead of stdout |
|
|
238
|
+
| `-f, --format` | Output format: `hex` (default, chunked) or `b32` |
|
|
239
|
+
|
|
240
|
+
### `btotp code`
|
|
241
|
+
|
|
242
|
+
Generate a TOTP code from a secret or vault account.
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
btotp code [-s SECRET | -a ACCOUNT] [-w] [--copy] [--algo ALGO] [--length N] [--step N]
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
| Flag | Description |
|
|
249
|
+
|---|---|
|
|
250
|
+
| `-s, --secret` | Secret key as a hex string |
|
|
251
|
+
| `-a, --account` | Account name in the vault |
|
|
252
|
+
| `-w, --watch` | Watch mode — refresh periodically (press Ctrl+C to stop) |
|
|
253
|
+
| `--copy` | Copy the code to clipboard instead of stdout |
|
|
254
|
+
| `--algo, --algorithm` | Hash algorithm: `sha1`, `sha256`, `sha512` (default: `sha512`) |
|
|
255
|
+
| `--length` | Code length in characters (default: 12) |
|
|
256
|
+
| `--step` | Time step in seconds (default: 45) |
|
|
257
|
+
|
|
258
|
+
### `btotp verify`
|
|
259
|
+
|
|
260
|
+
Verify a TOTP code against a secret or vault account.
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
btotp verify (-s SECRET | -a ACCOUNT) -c CODE [-n WINDOW] [--algo ALGO] [--length N] [--step N]
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
| Flag | Description |
|
|
267
|
+
|---|---|
|
|
268
|
+
| `-s, --secret` | Secret key as a hex string |
|
|
269
|
+
| `-a, --account` | Account name in the vault |
|
|
270
|
+
| `-c, --code` | Code to verify |
|
|
271
|
+
| `-n, --window` | Time drift window (default: 1 step in each direction) |
|
|
272
|
+
| `--algo, --algorithm` | Hash algorithm |
|
|
273
|
+
| `--length` | Code length |
|
|
274
|
+
| `--step` | Time step |
|
|
275
|
+
|
|
276
|
+
Exits with status 0 if valid, 1 if invalid.
|
|
277
|
+
|
|
278
|
+
### `btotp init`
|
|
279
|
+
|
|
280
|
+
Create a new encrypted vault.
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
btotp init
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Prompts for a master password (and confirmation). Creates `~/.config/btotp/vault.json`.
|
|
287
|
+
|
|
288
|
+
### `btotp add`
|
|
289
|
+
|
|
290
|
+
Add an account to the vault.
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
btotp add NAME [-s SECRET] [--b32 B32] [-u URI] [-g] [-i ISSUER] [--algo ALGO] [--digits N] [--period N]
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
| Argument | Description |
|
|
297
|
+
|---|---|
|
|
298
|
+
| `NAME` | Account name (required) |
|
|
299
|
+
| `-s, --secret` | Secret key as a hex string |
|
|
300
|
+
| `--b32` | Secret key as a base32 string |
|
|
301
|
+
| `-u, --uri` | Import from an `otpauth://` URI |
|
|
302
|
+
| `-g, --generate` | Generate a new random secret |
|
|
303
|
+
| `-i, --issuer` | Issuer name (e.g. "GitHub", "Google") |
|
|
304
|
+
| `--algo, --algorithm` | Hash algorithm (default: `sha512`) |
|
|
305
|
+
| `--digits` | Code length (default: 12) |
|
|
306
|
+
| `--period` | Time step in seconds (default: 45) |
|
|
307
|
+
|
|
308
|
+
### `btotp list`
|
|
309
|
+
|
|
310
|
+
List all accounts in the vault with their current TOTP codes.
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
btotp list
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### `btotp show`
|
|
317
|
+
|
|
318
|
+
Show the current TOTP code for a specific account.
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
btotp show NAME [--copy]
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
| Flag | Description |
|
|
325
|
+
|---|---|
|
|
326
|
+
| `NAME` | Account name |
|
|
327
|
+
| `--copy` | Copy the code to clipboard |
|
|
328
|
+
|
|
329
|
+
### `btotp remove`
|
|
330
|
+
|
|
331
|
+
Remove an account from the vault.
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
btotp remove NAME
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### `btotp rename`
|
|
338
|
+
|
|
339
|
+
Rename an account in the vault.
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
btotp rename OLD_NAME NEW_NAME
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### `btotp import-uri`
|
|
346
|
+
|
|
347
|
+
Import an account from an `otpauth://totp/...` URI.
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
btotp import-uri URI [-n NAME]
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
| Argument | Description |
|
|
354
|
+
|---|---|
|
|
355
|
+
| `URI` | The full `otpauth://totp/...` URI |
|
|
356
|
+
| `-n, --name` | Account name (defaults to the label from the URI) |
|
|
357
|
+
|
|
358
|
+
### `btotp export`
|
|
359
|
+
|
|
360
|
+
Export the vault as plaintext JSON (secrets are visible — handle with care).
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
btotp export [-o FILE]
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
| Flag | Description |
|
|
367
|
+
|---|---|
|
|
368
|
+
| `-o, --output` | Write to file instead of stdout |
|
|
369
|
+
|
|
370
|
+
### `btotp import`
|
|
371
|
+
|
|
372
|
+
Import accounts from a JSON file or stdin.
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
btotp import [FILE]
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
| Argument | Description |
|
|
379
|
+
|---|---|
|
|
380
|
+
| `FILE` | JSON file to import (reads from stdin if omitted) |
|
|
381
|
+
|
|
382
|
+
### `btotp uri`
|
|
383
|
+
|
|
384
|
+
Generate an `otpauth://totp/...` URI for a vault account (for mobile enrollment).
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
btotp uri NAME [--qr]
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
| Flag | Description |
|
|
391
|
+
|---|---|
|
|
392
|
+
| `NAME` | Account name |
|
|
393
|
+
| `--qr` | Display the URI as a QR code in the terminal |
|
|
394
|
+
|
|
395
|
+
### `btotp completion`
|
|
396
|
+
|
|
397
|
+
Generate shell completion script.
|
|
398
|
+
|
|
399
|
+
```
|
|
400
|
+
btotp completion {bash,zsh,fish}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Usage:**
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
# Bash
|
|
407
|
+
source <(btotp completion bash)
|
|
408
|
+
|
|
409
|
+
# Zsh
|
|
410
|
+
autoload -U bashcompinit && bashcompinit && source <(btotp completion zsh)
|
|
411
|
+
|
|
412
|
+
# Fish
|
|
413
|
+
btotp completion fish | source
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Requires `argcomplete` (`pip install bettertotp[completion]`).
|
|
417
|
+
|
|
418
|
+
### `btotp config`
|
|
419
|
+
|
|
420
|
+
View or modify persistent configuration.
|
|
421
|
+
|
|
422
|
+
```
|
|
423
|
+
btotp config [--show] [--set KEY=VALUE]
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
| Flag | Description |
|
|
427
|
+
|---|---|
|
|
428
|
+
| `--show` | Display current configuration |
|
|
429
|
+
| `--set` | Set a configuration value |
|
|
430
|
+
|
|
431
|
+
**Available keys:**
|
|
432
|
+
|
|
433
|
+
| Key | Type | Default | Description |
|
|
434
|
+
|---|---|---|---|
|
|
435
|
+
| `time_step` | int | 45 | Default time step in seconds |
|
|
436
|
+
| `code_length` | int | 12 | Default code length in characters |
|
|
437
|
+
| `hash_algo` | string | `"sha512"` | Default hash algorithm |
|
|
438
|
+
| `clipboard` | bool | `false` | Always copy codes to clipboard |
|
|
439
|
+
|
|
440
|
+
Example:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
btotp config --set time_step=30
|
|
444
|
+
btotp config --set code_length=8
|
|
445
|
+
btotp config --show
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### `btotp help`
|
|
449
|
+
|
|
450
|
+
Show help for any command.
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
btotp help [COMMAND]
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Examples:
|
|
457
|
+
|
|
458
|
+
```bash
|
|
459
|
+
btotp help
|
|
460
|
+
btotp help code
|
|
461
|
+
btotp help list
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Python API
|
|
467
|
+
|
|
468
|
+
### `generate_code(key_bytes, t=None, algorithm="sha512", code_length=12, time_step=45)`
|
|
469
|
+
|
|
470
|
+
Generate a TOTP code.
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
from btotp import generate_code
|
|
474
|
+
|
|
475
|
+
code = generate_code(b"your-secret-key")
|
|
476
|
+
code = generate_code(b"secret", algorithm="sha256", code_length=8, time_step=30)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### `verify_code(key_bytes, code, window=1, algorithm="sha512", code_length=12, time_step=45)`
|
|
480
|
+
|
|
481
|
+
Verify a TOTP code within a time window.
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
from btotp import verify_code
|
|
485
|
+
|
|
486
|
+
valid = verify_code(b"secret", code)
|
|
487
|
+
valid = verify_code(b"secret", code, algorithm="sha256", code_length=8)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### `generate_code_at(key_bytes, timestamp, ...)`
|
|
491
|
+
|
|
492
|
+
Generate a code for a specific Unix timestamp.
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
from btotp import generate_code_at
|
|
496
|
+
|
|
497
|
+
code = generate_code_at(b"secret", 1000000, time_step=30)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### `generate_secret(length=64)`
|
|
501
|
+
|
|
502
|
+
Generate a cryptographically random secret.
|
|
503
|
+
|
|
504
|
+
```python
|
|
505
|
+
from btotp import generate_secret
|
|
506
|
+
|
|
507
|
+
secret = generate_secret() # 64 random bytes
|
|
508
|
+
secret = generate_secret(32) # 32 random bytes
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### `Vault`
|
|
512
|
+
|
|
513
|
+
Encrypted vault for storing multiple secrets.
|
|
514
|
+
|
|
515
|
+
```python
|
|
516
|
+
from btotp import Vault
|
|
517
|
+
|
|
518
|
+
vault = Vault()
|
|
519
|
+
vault.unlock("master-password")
|
|
520
|
+
vault.add("email", secret.hex(), issuer="Google")
|
|
521
|
+
code = vault.code("email")
|
|
522
|
+
accounts = vault.list_accounts()
|
|
523
|
+
vault.remove("email")
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Methods:**
|
|
527
|
+
|
|
528
|
+
| Method | Description |
|
|
529
|
+
|---|---|
|
|
530
|
+
| `exists()` | Check if vault file exists |
|
|
531
|
+
| `create(password)` | Create a new vault |
|
|
532
|
+
| `unlock(password)` | Unlock an existing vault |
|
|
533
|
+
| `add(name, secret, ...)` | Add an account |
|
|
534
|
+
| `remove(name)` | Remove an account |
|
|
535
|
+
| `rename(old, new)` | Rename an account |
|
|
536
|
+
| `get(name)` | Get account details |
|
|
537
|
+
| `list_accounts()` | List all accounts |
|
|
538
|
+
| `code(name)` | Generate current code for account |
|
|
539
|
+
| `export_json()` | Export vault as JSON string |
|
|
540
|
+
| `import_json(data)` | Import accounts from JSON string |
|
|
541
|
+
|
|
542
|
+
### `parse_otpauth(uri)`
|
|
543
|
+
|
|
544
|
+
Parse an `otpauth://totp/...` URI into its components.
|
|
545
|
+
|
|
546
|
+
```python
|
|
547
|
+
from btotp import parse_otpauth
|
|
548
|
+
|
|
549
|
+
result = parse_otpauth("otpauth://totp/Example:user@example.com?secret=JBSWY3DPEHPK3PXP")
|
|
550
|
+
# {
|
|
551
|
+
# "secret": "...", # hex-encoded secret
|
|
552
|
+
# "issuer": "Example",
|
|
553
|
+
# "account": "user@example.com",
|
|
554
|
+
# "algorithm": "sha1",
|
|
555
|
+
# "digits": 12,
|
|
556
|
+
# "period": 45,
|
|
557
|
+
# }
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### `generate_uri(account, secret_hex, issuer="", ...)`
|
|
561
|
+
|
|
562
|
+
Generate an `otpauth://totp/...` URI.
|
|
563
|
+
|
|
564
|
+
```python
|
|
565
|
+
from btotp import generate_uri
|
|
566
|
+
|
|
567
|
+
uri = generate_uri("myaccount", secret.hex(), issuer="MyApp",
|
|
568
|
+
algorithm="sha256", digits=8, period=30)
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
## Configuration
|
|
574
|
+
|
|
575
|
+
### Vault file
|
|
576
|
+
|
|
577
|
+
Encrypted vault at `~/.config/btotp/vault.json`. The file contains:
|
|
578
|
+
|
|
579
|
+
```json
|
|
580
|
+
{
|
|
581
|
+
"version": 1,
|
|
582
|
+
"salt": "<base64>",
|
|
583
|
+
"nonce": "<base64>",
|
|
584
|
+
"data": "<AES-256-GCM encrypted, base64-encoded JSON blob>"
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
- **Encryption**: AES-256-GCM with random 12-byte nonce
|
|
589
|
+
- **Key derivation**: PBKDF2-HMAC-SHA256, 600,000 iterations, random 16-byte salt
|
|
590
|
+
- **Plaintext format** (when decrypted):
|
|
591
|
+
|
|
592
|
+
```json
|
|
593
|
+
{
|
|
594
|
+
"accounts": {
|
|
595
|
+
"my-account": {
|
|
596
|
+
"secret": "a1b2c3d4...",
|
|
597
|
+
"issuer": "Example",
|
|
598
|
+
"algorithm": "sha512",
|
|
599
|
+
"digits": 12,
|
|
600
|
+
"period": 45
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### User config
|
|
607
|
+
|
|
608
|
+
Persistent defaults at `~/.config/btotp/config.json`:
|
|
609
|
+
|
|
610
|
+
```json
|
|
611
|
+
{
|
|
612
|
+
"time_step": 30,
|
|
613
|
+
"code_length": 8,
|
|
614
|
+
"hash_algo": "sha256",
|
|
615
|
+
"clipboard": true
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## Security Considerations
|
|
622
|
+
|
|
623
|
+
- The vault master password is the single point of failure. Choose a strong password.
|
|
624
|
+
- `btotp export` outputs **plaintext JSON** including all secrets. Handle exports securely.
|
|
625
|
+
- The vault file itself is encrypted with AES-256-GCM, but the encryption strength depends on your master password.
|
|
626
|
+
- PBKDF2 iterations are set to 600,000 (OWASP 2023 recommendation for SHA-256).
|
|
627
|
+
- Secret keys default to 64 bytes (512 bits) of random data via `secrets.token_bytes()`.
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## Differences from Standard TOTP (RFC 6238)
|
|
632
|
+
|
|
633
|
+
| Aspect | Standard TOTP | BetterTOTP |
|
|
634
|
+
|---|---|---|
|
|
635
|
+
| Output | 6–8 decimal digits | 12 alphanumeric+special characters |
|
|
636
|
+
| Alphabet | `0-9` (10 chars) | `A-Z a-z 0-9 !@#$%&*-_+=?~` (75 chars) |
|
|
637
|
+
| Default hash | SHA-1 | SHA-512 |
|
|
638
|
+
| Default time step | 30 seconds | 45 seconds |
|
|
639
|
+
| Code generation | Dynamic truncation + modulo 10^N | Byte encoding into 75-char alphabet |
|
|
640
|
+
| Secret size | 10–20 bytes (80–160 bits) recommended | 64 bytes (512 bits) default |
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
## Changelog
|
|
645
|
+
|
|
646
|
+
### v1.0.0
|
|
647
|
+
|
|
648
|
+
- **Android app** — Native Kotlin + Jetpack Compose APK in `android/`
|
|
649
|
+
- Dual vault modes: Master Password (PBKDF2 + AES-256-GCM) or Biometric (Android Keystore)
|
|
650
|
+
- TOTP core reimplemented in pure Kotlin (HMAC-SHA1/256/512, 75-char charset)
|
|
651
|
+
- Material 3 UI with dynamic color, navigation, and auto-refreshing codes
|
|
652
|
+
- **Linux binary** — PyInstaller-based standalone executable in `linux/`
|
|
653
|
+
- Tkinter GUI with unlock, live code list, add/copy/delete
|
|
654
|
+
- Automatic GUI/CLI fallback based on display availability
|
|
655
|
+
- `otpauth://` URI handler registration via xdg-mime
|
|
656
|
+
- **Web API** — FastAPI microservice for website backend integration in `web/`
|
|
657
|
+
- Stateless enrollment and verification endpoints (non-standard BetterTOTP algorithm)
|
|
658
|
+
- CORS configurable, ready for any web framework
|
|
659
|
+
- FOSS — MIT License. Author: Shri Kant
|
|
660
|
+
|
|
661
|
+
### v0.2.0
|
|
662
|
+
|
|
663
|
+
- Configurable hash algorithm (`sha1`, `sha256`, `sha512`)
|
|
664
|
+
- Configurable code length and time step
|
|
665
|
+
- Encrypted vault (`btotp init`, `add`, `list`, `show`, `remove`, `rename`)
|
|
666
|
+
- `otpauth://totp/...` URI import (`btotp import-uri`)
|
|
667
|
+
- URI generation with optional QR display (`btotp uri --qr`)
|
|
668
|
+
- Clipboard copy support (`btotp code --copy`, `btotp show --copy`)
|
|
669
|
+
- User configuration (`btotp config`)
|
|
670
|
+
- Vault export/import (`btotp export`, `btotp import`)
|
|
671
|
+
- Shell completion scripts (`btotp completion`)
|
|
672
|
+
- `btotp help` subcommand
|
|
673
|
+
|
|
674
|
+
### v0.1.0
|
|
675
|
+
|
|
676
|
+
- Initial release
|
|
677
|
+
- `btotp generate-secret`, `btotp code`, `btotp verify`
|
|
678
|
+
- HMAC-SHA512 with 12-char codes, 45-second time step
|
|
679
|
+
- Watch mode (`--watch`)
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
## License
|
|
684
|
+
|
|
685
|
+
MIT
|