Files
2026-05-27 23:47:53 +01:00

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 / addSymbols injection at randomised positions.
  • Deterministic under seeded Rng.
  • Zod-validated; mismatch → PWGEN.INVALID_OPTIONS.
  • Emits pwgen.generate_duration_ms{mode=passphrase} and pwgen.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 (default 6): number of words to draw.
  • opts.wordlist (default "long"): "long" / "short" / registry name / inline string[].
  • opts.separator (default "-"): string inserted between words before decoration.
  • opts.capitalize (default "none"): "none" / "firstChar" / "randomWord".
  • opts.addDigits / opts.addSymbols (default 0): count of characters injected at randomised positions.
  • rng: defaults to defaultRng().

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.