yapg-py 0.4.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.
- yapg_py-0.4.0/LICENSE +21 -0
- yapg_py-0.4.0/PKG-INFO +481 -0
- yapg_py-0.4.0/README.md +435 -0
- yapg_py-0.4.0/pyproject.toml +75 -0
- yapg_py-0.4.0/setup.cfg +4 -0
- yapg_py-0.4.0/src/keygen/__init__.py +3 -0
- yapg_py-0.4.0/src/keygen/check.py +158 -0
- yapg_py-0.4.0/src/keygen/cli.py +505 -0
- yapg_py-0.4.0/src/keygen/core.py +245 -0
- yapg_py-0.4.0/src/keygen/data/WORDLISTS.md +15 -0
- yapg_py-0.4.0/src/keygen/data/licenses/CC0-1.0.txt +121 -0
- yapg_py-0.4.0/src/keygen/data/licenses/GFDL-1.3.txt +451 -0
- yapg_py-0.4.0/src/keygen/data/wordlist_de.txt +7776 -0
- yapg_py-0.4.0/src/keygen/data/wordlist_en.txt +7776 -0
- yapg_py-0.4.0/src/keygen/data/wordlist_es.txt +7776 -0
- yapg_py-0.4.0/src/keygen/infra.py +88 -0
- yapg_py-0.4.0/src/keygen/presets.py +102 -0
- yapg_py-0.4.0/src/keygen/selftest.py +94 -0
- yapg_py-0.4.0/src/keygen/server.py +150 -0
- yapg_py-0.4.0/src/keygen/totp.py +62 -0
- yapg_py-0.4.0/src/yapg_py.egg-info/PKG-INFO +481 -0
- yapg_py-0.4.0/src/yapg_py.egg-info/SOURCES.txt +28 -0
- yapg_py-0.4.0/src/yapg_py.egg-info/dependency_links.txt +1 -0
- yapg_py-0.4.0/src/yapg_py.egg-info/entry_points.txt +3 -0
- yapg_py-0.4.0/src/yapg_py.egg-info/requires.txt +26 -0
- yapg_py-0.4.0/src/yapg_py.egg-info/top_level.txt +1 -0
- yapg_py-0.4.0/tests/test_cli.py +74 -0
- yapg_py-0.4.0/tests/test_core.py +168 -0
- yapg_py-0.4.0/tests/test_features.py +198 -0
- yapg_py-0.4.0/tests/test_server.py +58 -0
yapg_py-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Antonio Elena
|
|
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.
|
yapg_py-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yapg-py
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Yet another password generator: API keys, passwords, Diceware passphrases (en/es/de), PINs, recovery codes, TOTP, SSH/WireGuard/JWT/htpasswd material. CLI, REST API and an offline web page.
|
|
5
|
+
Author: Antonio Elena
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/aelena/yapg
|
|
8
|
+
Project-URL: Repository, https://github.com/aelena/yapg
|
|
9
|
+
Project-URL: Issues, https://github.com/aelena/yapg/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/aelena/yapg/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Web page, https://aelena.com/yapg/
|
|
12
|
+
Keywords: password,api-key,passphrase,diceware,recovery-codes,totp,ssh,wireguard,secrets,cli
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Security
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: typer>=0.12
|
|
25
|
+
Provides-Extra: api
|
|
26
|
+
Requires-Dist: sanic>=23.6; extra == "api"
|
|
27
|
+
Provides-Extra: infra
|
|
28
|
+
Requires-Dist: cryptography>=42; extra == "infra"
|
|
29
|
+
Requires-Dist: bcrypt>=4.0; extra == "infra"
|
|
30
|
+
Provides-Extra: qr
|
|
31
|
+
Requires-Dist: qrcode>=7.4; extra == "qr"
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: sanic>=23.6; extra == "all"
|
|
34
|
+
Requires-Dist: cryptography>=42; extra == "all"
|
|
35
|
+
Requires-Dist: bcrypt>=4.0; extra == "all"
|
|
36
|
+
Requires-Dist: qrcode>=7.4; extra == "all"
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
40
|
+
Requires-Dist: sanic>=23.6; extra == "dev"
|
|
41
|
+
Requires-Dist: sanic-testing>=23.6; extra == "dev"
|
|
42
|
+
Requires-Dist: cryptography>=42; extra == "dev"
|
|
43
|
+
Requires-Dist: bcrypt>=4.0; extra == "dev"
|
|
44
|
+
Requires-Dist: qrcode>=7.4; extra == "dev"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
47
|
+
<p align="center">
|
|
48
|
+
<a href="https://github.com/aelena/yapg/actions/workflows/ci.yml"><img src="https://github.com/aelena/yapg/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
|
|
49
|
+
<img src="https://img.shields.io/badge/python-%3E%3D3.12-3776AB?logo=python&logoColor=fff" alt="Python >= 3.12" />
|
|
50
|
+
<img src="https://img.shields.io/badge/cli-typer-009688" alt="CLI: Typer" />
|
|
51
|
+
<img src="https://img.shields.io/badge/api-Sanic-ff69b4?logo=python&logoColor=fff" alt="API: Sanic" />
|
|
52
|
+
<img src="https://img.shields.io/badge/web-vanilla%20JS-F7DF1E?logo=javascript&logoColor=000" alt="Web: Vanilla JS" />
|
|
53
|
+
<img src="https://img.shields.io/badge/crypto-secrets%20%2F%20Web%20Crypto-blueviolet" alt="Crypto: secrets / Web Crypto" />
|
|
54
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="License: MIT" />
|
|
55
|
+
</p>
|
|
56
|
+
|
|
57
|
+
# yapg
|
|
58
|
+
|
|
59
|
+
**Yet another password generator**, except it is also the key generator, the passphrase generator, the TOTP bootstrapper and the "give me an SSH key / WireGuard pair / JWT secret / htpasswd line" tool you otherwise reach for five different programs to get.
|
|
60
|
+
|
|
61
|
+
One set of cryptographically secure generators, three ways in:
|
|
62
|
+
|
|
63
|
+
| | What | Needs |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `keygen` (alias `yapg`) | A CLI you install once and keep | Python 3.12+, `typer` |
|
|
66
|
+
| `keygen serve` | A stateless localhost REST API | the `[api]` extra |
|
|
67
|
+
| [aelena.com/yapg](https://aelena.com/yapg/) or `web/` | A single static page, works offline, nothing leaves the browser | nothing |
|
|
68
|
+
|
|
69
|
+
Every random byte comes from the operating system's CSPRNG: Python's [`secrets`](https://docs.python.org/3/library/secrets.html) on the command line, [`crypto.getRandomValues()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) in the browser. Nothing is logged, stored or sent anywhere, with one opt-in exception: `keygen check --hibp` sends five hex characters of a SHA-1 to Have I Been Pwned.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Install
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# everything, as a global tool (recommended)
|
|
77
|
+
uv tool install "yapg-py[all]"
|
|
78
|
+
|
|
79
|
+
# or plain pip, into any environment
|
|
80
|
+
pip install "yapg-py[all]"
|
|
81
|
+
|
|
82
|
+
# straight from git, or from a checkout
|
|
83
|
+
uv tool install "yapg-py[all] @ git+https://github.com/aelena/yapg"
|
|
84
|
+
uv tool install --editable ".[all]"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The distribution is `yapg-py` (plain `yapg` on PyPI belongs to an unrelated project); the import package is `keygen`.
|
|
88
|
+
|
|
89
|
+
Extras, if you want less: `[api]` adds Sanic, `[infra]` adds `cryptography` and `bcrypt` for `ssh`, `wireguard` and `htpasswd`, `[qr]` adds the ASCII QR code for `totp`. The core commands have no dependency beyond Typer.
|
|
90
|
+
|
|
91
|
+
Both `keygen` and `yapg` end up on your PATH and are the same program.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Commands
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
keygen key Opaque API keys / tokens
|
|
99
|
+
keygen password Random passwords with at least one character from every enabled class
|
|
100
|
+
keygen passphrase Diceware passphrases, English / Spanish / German
|
|
101
|
+
keygen pronounceable Nonsense words you can read out loud
|
|
102
|
+
keygen pin Numeric PINs
|
|
103
|
+
keygen recovery GitHub-style one-time recovery codes
|
|
104
|
+
keygen preset Named presets (stripe, github, db, wifi, vault, ...), yours included
|
|
105
|
+
keygen check Score a password or passphrase you already have
|
|
106
|
+
keygen totp new A TOTP secret with its otpauth:// URI and QR code
|
|
107
|
+
keygen totp code The current code for a TOTP secret
|
|
108
|
+
keygen ssh An ed25519 SSH key pair
|
|
109
|
+
keygen wireguard A WireGuard key pair (+ preshared key)
|
|
110
|
+
keygen jwt-secret An HMAC signing secret
|
|
111
|
+
keygen htpasswd A bcrypt basic-auth line
|
|
112
|
+
keygen selftest Chi-squared uniformity checks over every generator
|
|
113
|
+
keygen serve The REST API
|
|
114
|
+
keygen web The static page on localhost
|
|
115
|
+
keygen version
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Every generator takes `-n/--count` (1 to 500), `-o/--out FILE`, `-c/--copy` (clipboard) and `--json`. Values go to stdout one per line so they pipe cleanly; entropy, "copied", "wrote N to file" go to stderr.
|
|
119
|
+
|
|
120
|
+
### Keys
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
$ keygen key
|
|
124
|
+
Nf3S1x4aqS-6rYbp3yz2_hMVl6o9vPZpdTkZWFR9
|
|
125
|
+
|
|
126
|
+
$ keygen key -f hex -l 64
|
|
127
|
+
1f6b0c3e9a7d4b2e8c5f0a1d3e6b9c2f4a7d0e3b6c9f2a5d8e1b4c7f0a3d6e9b
|
|
128
|
+
|
|
129
|
+
$ keygen key -f alnum -l 24 -p sk_live_ -n 3 # bodies shortened here
|
|
130
|
+
sk_live_Qm8vT2xLp9Zr4Wn...
|
|
131
|
+
sk_live_7Fg2KjN5tRw8XqL...
|
|
132
|
+
sk_live_Bz4Yn9Wk2Tq7Lx1...
|
|
133
|
+
|
|
134
|
+
$ keygen key -f uuid --json
|
|
135
|
+
{
|
|
136
|
+
"keys": ["3f2a9c1e-7b4d-4e8a-9c2f-1a6b3d5e7f90"],
|
|
137
|
+
"count": 1,
|
|
138
|
+
"format": "uuid"
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Formats: `hex`, `base64url` (default), `alnum`, `uuid` (v4). Length counts characters after the prefix. Prefixes are limited to 64 printable, non-whitespace characters.
|
|
143
|
+
|
|
144
|
+
### Passwords
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
$ keygen password
|
|
148
|
+
k7$Vq2!mZp9@Lw4#Rt8x
|
|
149
|
+
|
|
150
|
+
$ keygen password -l 32 --no-symbols -x -e # no symbols, no I l 1 O 0, show entropy
|
|
151
|
+
Hx7kQm3NpW9vRt2YbJ4cZf8gKd6sLn5a
|
|
152
|
+
entropy: 186.4 bits
|
|
153
|
+
|
|
154
|
+
$ keygen password -n 50 -o passwords.txt
|
|
155
|
+
wrote 50 to passwords.txt
|
|
156
|
+
|
|
157
|
+
$ keygen password --json -l 16
|
|
158
|
+
{
|
|
159
|
+
"passwords": ["p4K$mN9!xQ2@vL7w"],
|
|
160
|
+
"count": 1,
|
|
161
|
+
"length": 16,
|
|
162
|
+
"upper": true, "lower": true, "digits": true, "symbols": true,
|
|
163
|
+
"exclude_ambiguous": false,
|
|
164
|
+
"entropy_bits": 104.9
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Toggles: `--no-upper`, `--no-lower`, `--no-digits`, `--no-symbols`, `-x/--exclude-ambiguous`. Every enabled class is guaranteed at least once; the rest of the characters are uniform over the combined alphabet and the whole thing is Fisher-Yates shuffled.
|
|
169
|
+
|
|
170
|
+
### Passphrases
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
$ keygen passphrase
|
|
174
|
+
surpass-shredding-clanking-saline-direction-unsavory
|
|
175
|
+
|
|
176
|
+
$ keygen passphrase -w 5 -s " " -C -d # five words, spaces, Title Case, one digit
|
|
177
|
+
Banana Salt Rewrap Retail3 Update
|
|
178
|
+
|
|
179
|
+
$ keygen passphrase -L es -w 4
|
|
180
|
+
almohada-tortuga-nevera-bosque
|
|
181
|
+
|
|
182
|
+
$ keygen passphrase -L de -w 4 -e
|
|
183
|
+
klinke-wolke-fahrrad-segeln
|
|
184
|
+
entropy: 51.7 bits
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Three bundled 7776-word Diceware lists, 12.9 bits per word: the EFF large list (`en`), dadoware-bonito-es (`es`), dys2p wordlists-de (`de`). Sources and licences are in [src/keygen/data/WORDLISTS.md](src/keygen/data/WORDLISTS.md). `--capitalize` and `--digit` satisfy composition rules; they add negligible entropy.
|
|
188
|
+
|
|
189
|
+
### Pronounceable
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
$ keygen pronounceable
|
|
193
|
+
trobaklipuston
|
|
194
|
+
|
|
195
|
+
$ keygen pronounceable -s 3 -C -n 3 -e
|
|
196
|
+
Kefrilon
|
|
197
|
+
Bozustai
|
|
198
|
+
Grunopla
|
|
199
|
+
entropy: 34.7 bits
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Consonant-vowel-coda syllables, about 11.3 bits each. For the password you have to say over the phone.
|
|
203
|
+
|
|
204
|
+
### PINs and recovery codes
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
$ keygen pin
|
|
208
|
+
481926
|
|
209
|
+
|
|
210
|
+
$ keygen pin -l 8 -n 3
|
|
211
|
+
20471139
|
|
212
|
+
88305162
|
|
213
|
+
57712904
|
|
214
|
+
|
|
215
|
+
$ keygen recovery
|
|
216
|
+
# Recovery codes. Keep these somewhere safe; each one works once.
|
|
217
|
+
|
|
218
|
+
t6zut-6egw7
|
|
219
|
+
nudhv-x9b42
|
|
220
|
+
... (16 by default)
|
|
221
|
+
|
|
222
|
+
$ keygen recovery -n 4 -l 12 -g 3 -e
|
|
223
|
+
h67z-bjmn-nc9c
|
|
224
|
+
7p2k-xw4r-tf8d
|
|
225
|
+
mq3s-vz9b-yk5n
|
|
226
|
+
c8dw-rn2h-gj6p
|
|
227
|
+
entropy: 59.4 bits
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Recovery codes use the alphabet `abcdefghjkmnpqrstuvwxyz23456789`: no `0 O 1 I l`, so they survive being read off paper.
|
|
231
|
+
|
|
232
|
+
### Presets
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
$ keygen preset
|
|
236
|
+
stripe key format='alnum', length=24, prefix='sk_live_' Stripe-style secret key
|
|
237
|
+
stripe-test key format='alnum', length=24, prefix='sk_test_' Stripe-style test key
|
|
238
|
+
github key format='alnum', length=36, prefix='ghp_' GitHub-style personal access token
|
|
239
|
+
api-hex key format='hex', length=64 256-bit hex token
|
|
240
|
+
api-urlsafe key format='base64url', length=43 256-bit base64url token
|
|
241
|
+
session key format='base64url', length=32 Session cookie value
|
|
242
|
+
db password length=32, symbols=False Database password: 32 alphanumerics, safe in connection strings
|
|
243
|
+
wifi passphrase words=4, separator='-', capitalize=True WPA passphrase a guest can type
|
|
244
|
+
vault passphrase words=7, separator=' ' Master passphrase, about 90 bits
|
|
245
|
+
phone pronounceable syllables=4, capitalize=True Temporary password you can read out loud
|
|
246
|
+
pin pin length=6 Six-digit PIN
|
|
247
|
+
2fa-backup recovery length=10, groups=2 Recovery code, xxxxx-xxxxx
|
|
248
|
+
|
|
249
|
+
$ keygen preset db
|
|
250
|
+
Hx7kQm3NpW9vRt2YbJ4cZf8gKd6sLn5a
|
|
251
|
+
|
|
252
|
+
$ keygen preset github -n 2
|
|
253
|
+
ghp_Qm8vT2xLp9Zr4WnK1cHbYd3e...
|
|
254
|
+
ghp_Bz4Yn9Wk2Tq7Lx1Mr8Jv5Pd3...
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Your own presets live in `~/.config/keygen/presets.toml` (or wherever `$KEYGEN_PRESETS` points) and override built-ins by name:
|
|
258
|
+
|
|
259
|
+
```toml
|
|
260
|
+
[stripe]
|
|
261
|
+
command = "key"
|
|
262
|
+
format = "alnum"
|
|
263
|
+
length = 24
|
|
264
|
+
prefix = "sk_live_"
|
|
265
|
+
|
|
266
|
+
[vault]
|
|
267
|
+
command = "passphrase"
|
|
268
|
+
words = 7
|
|
269
|
+
lang = "es"
|
|
270
|
+
separator = " "
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`command` is any of `key`, `password`, `passphrase`, `pronounceable`, `pin`, `recovery`; the other keys are that command's options.
|
|
274
|
+
|
|
275
|
+
### Check
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
$ keygen check 'correct-horse-battery-staple'
|
|
279
|
+
length 28
|
|
280
|
+
classes lower, symbols
|
|
281
|
+
entropy 51.7 bits (fair)
|
|
282
|
+
looks like 4 words from the en list: 51.7 bits
|
|
283
|
+
|
|
284
|
+
$ keygen check 'Password1234!!!'
|
|
285
|
+
length 15
|
|
286
|
+
classes upper, lower, digits, symbols
|
|
287
|
+
entropy 53.3 bits (fair)
|
|
288
|
+
warning a character repeated three or more times in a row
|
|
289
|
+
warning keyboard/sequence run '1234'
|
|
290
|
+
warning contains a very common password word
|
|
291
|
+
|
|
292
|
+
$ echo 'qwerty12' | keygen check; echo "exit $?"
|
|
293
|
+
length 8
|
|
294
|
+
classes lower, digits
|
|
295
|
+
entropy 26.4 bits (weak)
|
|
296
|
+
warning shorter than 12 characters
|
|
297
|
+
warning keyboard/sequence run 'qwer'
|
|
298
|
+
exit 1
|
|
299
|
+
|
|
300
|
+
$ keygen check --hibp 'hunter2'
|
|
301
|
+
...
|
|
302
|
+
hibp seen 17,043 times in breaches
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The estimate is `length × log2(pool)`, capped at `words × 12.9` when every token is in one of the bundled wordlists, minus 15 bits per pattern warning. It is a heuristic, not zxcvbn: its job is to keep obvious junk from scoring "fair". Exit code 1 means weak or breached, so it works in scripts. `--hibp` uses the [k-anonymity range API](https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange): only the first five hex characters of the SHA-1 leave your machine.
|
|
306
|
+
|
|
307
|
+
### TOTP
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
$ keygen totp new --issuer "My App" --account ana@example.com
|
|
311
|
+
secret JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP
|
|
312
|
+
uri otpauth://totp/My%20App%3Aana%40example.com?secret=JBSW...&digits=6&period=30&algorithm=SHA1&issuer=My+App
|
|
313
|
+
current 492039 (17s left)
|
|
314
|
+
█▀▀▀▀▀█ ▄▀ ▀▄█ █▀▀▀▀▀█
|
|
315
|
+
█ ███ █ ▀█▄▀▄ █ ███ █ (scan with any authenticator app)
|
|
316
|
+
...
|
|
317
|
+
|
|
318
|
+
$ keygen totp code JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP
|
|
319
|
+
492039
|
|
320
|
+
17s left
|
|
321
|
+
|
|
322
|
+
$ keygen totp new --no-qr --json --digits 8 --algorithm SHA256
|
|
323
|
+
{ "secret": "...", "uri": "otpauth://totp/...", "current_code": "48213907", ... }
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
RFC 6238, verified against the RFC's own test vectors. The QR code needs the `[qr]` extra; without it the URI is still printed. `totp code` is handy for testing a login flow you are building, or as the authenticator of last resort.
|
|
327
|
+
|
|
328
|
+
### SSH, WireGuard, JWT, htpasswd
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
$ keygen ssh -C ana@laptop -o ~/.ssh/id_ed25519_new
|
|
332
|
+
wrote ~/.ssh/id_ed25519_new and ~/.ssh/id_ed25519_new.pub
|
|
333
|
+
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK... ana@laptop
|
|
334
|
+
|
|
335
|
+
$ keygen ssh # no -o: both keys to stdout
|
|
336
|
+
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
337
|
+
...
|
|
338
|
+
-----END OPENSSH PRIVATE KEY-----
|
|
339
|
+
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK...
|
|
340
|
+
|
|
341
|
+
$ keygen wireguard --preshared
|
|
342
|
+
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
|
|
343
|
+
PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
|
|
344
|
+
PresharedKey = FpCyhws9cxwWoV4xELtfJvjJN+zQVRPISllRWgeopVE=
|
|
345
|
+
|
|
346
|
+
$ keygen jwt-secret # 256 bits, base64url
|
|
347
|
+
k3rT9xQ2mZ8vLp5wNb7cYf4dHs6gJa1eRu0iXo2tVq
|
|
348
|
+
|
|
349
|
+
$ keygen jwt-secret -b 512 -E hex
|
|
350
|
+
7f3a... (128 hex chars)
|
|
351
|
+
|
|
352
|
+
$ keygen htpasswd ana # generates the password and prints it to stderr
|
|
353
|
+
password: Hx7kQm3NpW9vRt2YbJ4cZf8g
|
|
354
|
+
ana:$2y$12$Qm8vT2xLp9Zr4WnK1cHbYeB3e7Fg2KjN5tRw8XqLm3ZpVc6HsN7uK
|
|
355
|
+
|
|
356
|
+
$ keygen htpasswd ana --prompt -o .htpasswd # asks twice, appends the line
|
|
357
|
+
$ keygen htpasswd ana -P 's3cret' --rounds 10
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`ssh` refuses to overwrite an existing file and writes the private key with mode 0600 on POSIX. `wireguard` prints lines you paste straight into `[Interface]` and `[Peer]`. `htpasswd` emits bcrypt with the `$2y$` prefix Apache documents, which nginx, Caddy and Traefik accept too. These four need the `[infra]` extra and are deliberately not exposed by the REST API.
|
|
361
|
+
|
|
362
|
+
### Self-test
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
$ keygen selftest
|
|
366
|
+
ok hex chi2 p=0.412 over 25600 symbols
|
|
367
|
+
ok base64url chi2 p=0.187 over 25600 symbols
|
|
368
|
+
ok alnum chi2 p=0.903 over 25600 symbols
|
|
369
|
+
ok recovery chi2 p=0.655 over 12800 symbols
|
|
370
|
+
ok pin chi2 p=0.271 over 12800 symbols
|
|
371
|
+
ok password chi2 p=0.544 over 25600 symbols
|
|
372
|
+
ok password classes one of each class at length 4
|
|
373
|
+
ok wordlist en 7776 words, 7776 unique
|
|
374
|
+
ok wordlist es 7776 words, 7776 unique
|
|
375
|
+
ok wordlist de 7776 words, 7776 unique
|
|
376
|
+
ok passphrase draw chi2 p=0.731 over 16 buckets
|
|
377
|
+
ok uuid v4 version/variant bits, no repeats
|
|
378
|
+
12/12 passed
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
A chi-squared goodness-of-fit test per alphabet plus the structural guarantees. It cannot prove randomness; it catches the bugs that matter (a truncated alphabet, modulo bias, a wordlist that lost a line). CI runs it on every push.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## REST API
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
keygen serve # http://127.0.0.1:8000
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
All endpoints are `GET`, return JSON, accept `count`, and accept `download=1` to receive a `text/plain` attachment instead. Bad parameters return `400` with a message.
|
|
392
|
+
|
|
393
|
+
| Endpoint | Parameters |
|
|
394
|
+
|---|---|
|
|
395
|
+
| `/health` | |
|
|
396
|
+
| `/api-key` | `format`, `length`, `prefix` |
|
|
397
|
+
| `/password` | `length`, `upper`, `lower`, `digits`, `symbols`, `exclude_ambiguous` (0/1) |
|
|
398
|
+
| `/passphrase` | `words`, `sep`, `lang` (en/es/de), `capitalize`, `digit` |
|
|
399
|
+
| `/pronounceable` | `syllables`, `capitalize` |
|
|
400
|
+
| `/pin` | `length` |
|
|
401
|
+
| `/recovery-codes` | `length`, `groups` |
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
$ curl "http://localhost:8000/api-key?prefix=sk_live_&length=32"
|
|
405
|
+
{"keys":["sk_live_aBcDeFgHiJkLmNoP..."],"count":1,"format":"base64url"}
|
|
406
|
+
|
|
407
|
+
$ curl "http://localhost:8000/passphrase?words=5&lang=es&sep=%20"
|
|
408
|
+
{"passphrases":["tortuga nevera bosque almohada faro"],"count":1,"words":5,"lang":"es","entropy_bits":64.6}
|
|
409
|
+
|
|
410
|
+
$ curl -O -J "http://localhost:8000/recovery-codes?download=1"
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
The API binds to localhost, has no authentication and no rate limiting. It is a local convenience, not a service to expose. Key material (SSH, WireGuard, htpasswd, TOTP) is CLI-only on purpose.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Web page
|
|
418
|
+
|
|
419
|
+
[aelena.com/yapg](https://aelena.com/yapg/), or open `web/index.html`, or `keygen web`. Seven tabs (API key, password, passphrase in three languages, pronounceable, PIN, recovery, the built-in presets), copy and download buttons, an entropy meter. No build step, no network access; the wordlists ship inside the page.
|
|
420
|
+
|
|
421
|
+
The page reimplements the generators in `web/app.js` by hand; it does not call the API. Keep it in step with `src/keygen/core.py`.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## How it works
|
|
426
|
+
|
|
427
|
+
**Randomness.** `secrets` in Python; `crypto.getRandomValues()` in the browser with rejection sampling in `randInt`, so there is no modulo bias.
|
|
428
|
+
|
|
429
|
+
**Passwords.** One guaranteed character per enabled class, the rest uniform over the combined alphabet, then a Fisher-Yates shuffle with the CSPRNG. Reported entropy is `length × log2(pool)`, an upper bound; the one-of-each rule costs a few bits at short lengths.
|
|
430
|
+
|
|
431
|
+
**Passphrases.** Diceware: 7776 words, 12.925 bits each, six words is 77.5 bits.
|
|
432
|
+
|
|
433
|
+
**Recovery codes.** 31-symbol alphabet, 4.95 bits per symbol, ten symbols is 49.5 bits. Enough for a code that is single-use and rate-limited.
|
|
434
|
+
|
|
435
|
+
**Limits.**
|
|
436
|
+
|
|
437
|
+
| | Min | Max | Default |
|
|
438
|
+
|---|---|---|---|
|
|
439
|
+
| Key length | 8 | 256 | 40 |
|
|
440
|
+
| Password length | 4 | 128 | 20 |
|
|
441
|
+
| Passphrase words | 3 | 20 | 6 |
|
|
442
|
+
| Pronounceable syllables | 2 | 16 | 4 |
|
|
443
|
+
| PIN digits | 4 | 32 | 6 |
|
|
444
|
+
| Recovery code length | 6 | 32 | 10 |
|
|
445
|
+
| Batch (`count`) | 1 | 500 | 1 (16 for recovery) |
|
|
446
|
+
| Prefix | | 64 chars, printable, no whitespace | |
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Project layout
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
yapg/
|
|
454
|
+
├── src/keygen/
|
|
455
|
+
│ ├── core.py the generators; pure, no I/O
|
|
456
|
+
│ ├── check.py scoring + HIBP lookup
|
|
457
|
+
│ ├── totp.py RFC 6238
|
|
458
|
+
│ ├── infra.py ssh, wireguard, jwt-secret, htpasswd
|
|
459
|
+
│ ├── presets.py built-in + user presets
|
|
460
|
+
│ ├── selftest.py chi-squared checks
|
|
461
|
+
│ ├── cli.py typer app -> keygen / yapg
|
|
462
|
+
│ ├── server.py sanic app -> keygen serve
|
|
463
|
+
│ └── data/ wordlists + licences
|
|
464
|
+
├── web/ static page, deployed to GitHub Pages
|
|
465
|
+
├── api/server.py compatibility shim
|
|
466
|
+
├── tests/ pytest
|
|
467
|
+
└── .github/workflows/ CI (ubuntu + windows, 3.12 + 3.13) and Pages
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Development
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
uv venv && uv pip install -e ".[dev]" # add --link-mode=copy on OneDrive-synced folders
|
|
474
|
+
pytest -p no:warnings
|
|
475
|
+
ruff check src tests
|
|
476
|
+
keygen selftest
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## Licence
|
|
480
|
+
|
|
481
|
+
[MIT](LICENSE). Wordlists: EFF (CC-BY 3.0), dadoware-bonito-es (GFDL 1.3), dys2p (CC0); see [WORDLISTS.md](src/keygen/data/WORDLISTS.md).
|