Deterministic password generator and profile manager for CLI workflows.
cpassgen stores password profiles (username/resource + constraints) and generates the same password for the same input
set every time.
- Multi-command CLI:
create,bump,get,list,sync - File-based profile repository with deterministic sharded layout
- Git sync: add/commit/push changes to remote, pull updates, conflict detection
- Constraint-driven password generation with stable pseudo-random expansion
- Generation versioning: version embedded in derivation string, version history stored
- Constraints cannot be changed without bumping generation version
- Explicit validation and clear CLI errors (
exit code 1)
cpassgen generates passwords deterministically. The generation_version
is included in the input string so different versions produce different passwords.
- Input seed:
SHA256(username:resource:version:secret) - Seed is expanded by deterministic HMAC-SHA256 stream generator
- Password is built to satisfy quotas:
length,upper,lower,digits,specials,mask
upper,lower,digitsandspecialsgenerate exactly that many characters;maskescapes that many special characters; remaining slots are filled with lowercase characters only- Character order is deterministically shuffled
python3 -m venv .venv
source .venv/bin/activate
pip install poetry
poetry installcpassgen reads configuration from three sources, merged with the following
priority (lowest to highest):
- A config file in the home directory:
~/.cpassgen/cpassgen.json - A config file passed via the global
--config PATHargument - Environment variables
Config files are JSON objects:
{
"git_persistence_path": "/path/to/profiles",
"key_word": "my-secret"
}Environment variables:
PASS_GEN_GIT_PERSISTENCE_PATH- directory where profiles are storedPASS_GEN_KEY_WORD- secret for thegetcommand (if set, prompt is skipped)
Environment variables override config file values; the --config file
overrides the home config file.
Example:
export PASS_GEN_GIT_PERSISTENCE_PATH="$HOME/.cpassgen/repo"
export PASS_GEN_KEY_WORD="my-secret"Or via a config file:
cat > "$HOME/.cpassgen/cpassgen.json" <<'EOF'
{
"git_persistence_path": "~/.cpassgen/repo",
"key_word": "my-secret"
}
EOFThe sync command and automatic sync prompts require a git repository with an origin remote:
export PASS_GEN_GIT_PERSISTENCE_PATH="$HOME/.cpassgen/repo"
git -C "$HOME/.cpassgen/repo" init
git -C "$HOME/.cpassgen/repo" remote add origin <your-remote-url>
git -C "$HOME/.cpassgen/repo" commit --allow-empty -m "initial" # optionalRun CLI:
cpassgen --helpShow the installed package version:
cpassgen --versionCreate profile:
cpassgen create \
--username user1 \
--resource example.comCreate profile with custom generation version:
cpassgen create \
--username user1 \
--resource example.com \
--generation-version 3Update existing profile constraints:
cpassgen bump \
--username user1 \
--resource example.com \
--length 16 \
--upper 2 \
--lower 4 \
--digits 2 \
--specials 2 \
--mask 1Generate password from profile:
cpassgen get \
--username user1 \
--resource example.comList stored profiles (one line per profile: username@resource v<version> <created_at>):
cpassgen listShow current generation constraints and version history for a profile:
cpassgen history \
--username user1 \
--resource example.comInclude the generated passwords for the current and past generations:
cpassgen history \
--username user1 \
--resource example.com \
--with-passwordsSync profile storage with remote git repository:
cpassgen syncPrint resolved configuration values (the secret key_word is omitted):
cpassgen get-configNotes:
createprompts for constraints interactively (with defaults); they can also be passed via--length,--upper,--lower,--digits,--specials,--maskcreatefails if profile already existsbumpfails if profile does not existgetfails if profile does not existhistoryfails if profile does not existlistprintsNo profiles foundwhen the repository is empty- after successful
createorbump, the tool prompts to sync changes with the remote repository (skipped in non-interactive mode) synccommits uncommitted changes, pulls remote updates via rebase, pushes local changes; on conflict prints detailed resolution instructions- all failures are returned as human-readable
Error: ...messages bumpautomatically increments thegeneration_version; constraint changes are optional- each constraint change via
bumprecords the previous state inversion_historyinside the profile JSON
- Keep one profile per real account identity (
username + resource) - Use
bumpto evolve constraints gradually instead of recreating profiles - Store secrets in environment variables or secure prompt input, not shell history
- Keep persistence path in private local storage and back it up securely
- Validate constraint changes in CI (
make lint,make test) before sharing - Prefer deterministic generation for reproducible recovery workflows
make fmt # isort + black
make fmt_check # formatting check
make lint # flake8
make test # pytest + coverageRun one test module:
pytest tests/test_main.pyapp/main.py CLI entrypoint (Click group + commands)
app/config.py configuration loading (files + environment variables)
app/generator.py deterministic password generation logic
app/persistence.py profile repository and filesystem layout
app/sync_service.py git sync, commit, push, pull, conflict detection
app/seed_expander.py deterministic pseudo-random byte stream
app/models.py immutable domain models
app/validators.py constraints validation
tests/ unit tests
pyproject.toml Poetry project manifest
Makefile dev shortcuts
Apache License 2.0. See LICENSE.