4.2 KiB
passphrase
(no submodules)
Purpose
generatePassphrase(opts, rng?) produces an EFF-style Diceware passphrase by drawing length words uniformly (via the bias-free randomInt) from the wordlist resolved via opts.wordlist. The default — 6 words from the EFF Long Wordlist joined with - — gives ≈ 77.55 bits of entropy, comfortably above the 75-bit floor recommended for symmetric-key-equivalent secrets. Diceware-style passphrases are dramatically more memorable than random char-class passwords at equivalent entropy, which is why the vault item editor in @suaveplan/vault defaults to this mode for the "Generate password" surface.
The function honours configurable separator, three capitalisation modes (none / firstChar / randomWord), and optional addDigits / addSymbols injections at random positions in the joined output. The decoration knobs exist so the same generator can produce strings that satisfy downstream "must contain a digit" composition rules without abandoning the Diceware backbone.
Features
- Bundled EFF Long (7776) and EFF Short 1 (1296) wordlists via tree-shakeable subpath imports.
- Caller-supplied
string[]wordlists allowed past the 256-entry min-size guard. - Configurable separator (
default "-"). capitalize: "none" | "firstChar" | "randomWord".addDigits/addSymbolsinjection at randomised positions.- Deterministic under seeded
Rng. - Zod-validated; mismatch →
PWGEN.INVALID_OPTIONS. - Emits
pwgen.generate_duration_ms{mode=passphrase}andpwgen.entropy_bits{mode=passphrase}histograms.
Usage: Basic Example
import { generatePassphrase } from "@suaveplan/password-generator";
const pp = generatePassphrase({}); // 6 words, EFF Long, joined with "-"
Usage: Advanced Example
import { generatePassphrase } from "@suaveplan/password-generator";
// 5 short words capitalised at random, two trailing digits, one symbol
const pp = generatePassphrase({
length: 5,
wordlist: "short",
capitalize: "randomWord",
addDigits: 2,
addSymbols: 1,
separator: " ",
});
// Caller-supplied themed wordlist (must be ≥ 256 entries)
const dnd = [/* ... 300 fantasy nouns ... */];
const themed = generatePassphrase({ wordlist: dnd, length: 4 });
API Reference
generatePassphrase(opts?: GeneratePassphraseOptions, rng?: Rng): string
opts.length(default6): number of words to draw.opts.wordlist(default"long"):"long"/"short"/ registry name / inlinestring[].opts.separator(default"-"): string inserted between words before decoration.opts.capitalize(default"none"):"none"/"firstChar"/"randomWord".opts.addDigits/opts.addSymbols(default0): count of characters injected at randomised positions.rng: defaults todefaultRng().
Throws PwgenError(PWGEN.INVALID_OPTIONS) on Zod mismatch, PwgenError(PWGEN.WORDLIST_TOO_SHORT) for inline arrays below 256 entries, and PwgenError(PWGEN.WORDLIST_NOT_REGISTERED) for unknown registry names.
Implementation Notes
Wordlist resolution lazily routes through getWordlist(name) so the registry's min-size guard applies uniformly — there's no special-case for the bundled EFF lists. Inline string[] wordlists bypass the registry but go through the same 256-entry floor inline, so a caller passing a tiny array sees the same PWGEN.WORDLIST_TOO_SHORT they would see from registerWordlist.
Decoration is applied after joining: addDigits / addSymbols inserts characters at uniformly-random positions in the already-joined string. This means a digit can land mid-word (e.g. hor5se-rifle) — that's intentional because inserting only at word boundaries would create a small-cardinality position distribution that's easier to fingerprint. Per-class injection uses bias-free randomInt against the DIGITS / SYMBOLS sets defined in the charsets module.
Entropy reporting uses estimatePassphraseEntropy(words, wordlistSize) against the picked words (not the post-decoration string), so the reported figure reflects the Diceware contribution. Decoration adds a small amount of entropy on top of that, but the package does not currently roll that into the reported figure to keep the formula stable across runs.