Skip to main content

Local encrypted password manager that lives in your terminal

Project description

pw-manager

A local, encrypted password manager that lives in your terminal. No browser, no cloud, no daemon — one encrypted file on disk and a small Python CLI in front of it.

$ python pw.py get github
master password:
username: dt@example.com
password copied to clipboard

Install

From PyPI (recommended — pipx keeps CLI tools isolated):

pipx install basic-password-manager    # or: pip install basic-password-manager
pw init

From source:

git clone https://github.com/Daisentaur/password-manager && cd password-manager
pipx install .

For development: python3 -m venv venv && ./venv/bin/pip install -e .

Usage

Run pw with no arguments for the interactive TUI: unlock, then / to filter, Enter to copy the selected password, n to add, e to edit, d to delete, t to switch themes (persisted across runs), q to quit. The search box matches every word of your query anywhere in an entry — name, username, notes, and the password itself — with matches highlighted in the theme's accent color (password matches show the row but highlight nothing, since passwords are never displayed). Rarer operations live in the command palette (ctrl+p): importing a browser CSV, changing the master password. The bundled themes — muted-slate (default), dawn, matrix — are borrowed with admiration from tuxedo; Textual's built-in themes are in the picker too.

Or use the subcommands:

Command What it does
pw init create a new empty vault
pw add github store an entry (prompts for username/password/notes)
pw add github --gen same, but generates a strong random password
pw get github copy the password to the clipboard, show the username
pw edit github update an entry field by field (Enter keeps current)
pw passwd change the master password
pw ls list entry names
pw rm github delete an entry
pw find "dt bank" search names, usernames, notes — every word must match, any order
pw gen -l 32 just print a random password
pw import passwords.csv import a browser CSV export (see below)

The vault lives at ~/.local/share/pw-manager/vault. Set the PW_VAULT environment variable to put it somewhere else.

The notes field takes any text you want kept secret alongside the password — recovery codes, PINs, the answer to "what was your first pet".

Moving off browser password storage

  1. Export: Chrome → chrome://password-manager/settings → "Export passwords". Firefox → about:logins → ⋯ menu → "Export passwords". Both produce a CSV.
  2. pw import passwords.csv
  3. Delete the CSV — it is every password you own in plaintext: shred -u passwords.csv
  4. Turn off saving and delete saved passwords in the browser settings.

How it works

Three layers, ~120 lines total. Read them in this order:

1. crypto.py — password → key, key → ciphertext

An AES key must be 32 unpredictable bytes; your master password is neither. Argon2id bridges the gap: derive_key(password, salt) always returns the same 32 bytes for the same inputs — that's what makes unlocking possible. It is deliberately slow and memory-hungry (64 MiB per guess), so an attacker who steals the vault file can't brute-force short passwords on a GPU the way they could against a plain hash.

The salt is 16 random bytes stored unencrypted in the vault file. It's not a secret: its job is making your derived key unique so precomputed attack tables are useless.

Encryption is AES-256-GCM, which is authenticated: decrypting with the wrong key, or decrypting data that was modified by even one bit, fails loudly instead of returning garbage. Each encryption uses a fresh random 12-byte nonce (also stored unencrypted — also not a secret). The one iron rule of GCM is that a (key, nonce) pair must never repeat, which is why it's random every time.

2. vault.py — the file format

[4 bytes "PWV1"][16-byte salt][12-byte nonce][ciphertext...]

The ciphertext is just your entries as JSON, encrypted. Load = read → derive key → decrypt → json.loads. Save = the reverse, with a fresh salt and nonce, written to a temp file and atomically renamed so a crash mid-write can't destroy the vault.

Your master password is never stored anywhere, in any form. "Wrong password" is detected purely by GCM authentication failing.

3. cli.py and tui.py — the interfaces

Both are thin skins over the same two modules — tui.py (built with Textual) holds the decrypted entries in memory for the session; the CLI re-derives the key per command.

argparse subcommands over the two modules above. Passwords are copied to the clipboard (wl-copy/xclip/xsel, whichever exists) rather than printed, so they don't sit in your terminal scrollback. CLI find searches names, usernames, and notes — deliberately not passwords, so fragments of secrets never land in your shell history; the TUI search box does cover passwords, because nothing typed there is logged anywhere.

Backups

Every save automatically keeps the previous version as vault.bak next to the vault — one-step undo if a save goes wrong or you delete the wrong entry.

That protects against mistakes, not against the disk dying. For that, the vault is one file: copy it anywhere — another disk, a USB stick, even somewhere untrusted, since it's useless without the master password.

cp ~/.local/share/pw-manager/vault /some/backup/location/

Troubleshooting

"I forgot the master password." The data is gone. Not "gone until support resets it" — mathematically gone; that's the entire design. This is why you keep the master password memorable (a long passphrase beats Tr0ub4dor&3) and why backups protect you from file loss but not from forgetting.

"wrong master password (or the vault file is corrupted)" — 99% of the time it's a typo'd password. If you're certain the password is right, the file was damaged; restore the automatic backup (cp ~/.local/share/pw-manager/vault.bak ~/.local/share/pw-manager/vault) or an off-machine copy.

Deleted or overwrote an entry by mistake — the state before your last save is in vault.bak; restore it as above. Only one generation is kept, so do it before the next save.

"not a vault file (bad magic bytes)" — the file at the vault path isn't one of ours (truncated, overwritten, or wrong path). Check echo $PW_VAULT and restore from a backup.

"no vault at ..." — run pw init, or PW_VAULT points somewhere unexpected.

Password gets printed instead of copied — no clipboard tool was found. Install one: sudo apt install wl-clipboard (Wayland) or xclip (X11).

Unlock feels slow (~1s) — that's Argon2 doing its job; the delay is the brute-force resistance. It's per-command, not per-keystroke.

MemoryError from Argon2 — the KDF needs 64 MiB free; something is eating your RAM.

Import brought in junk entries — browser CSVs include everything, even ancient accounts. pw rm <name> the ones you don't want, or edit the CSV before importing.

Honest limitations

  • No clipboard auto-clear: the password stays on the clipboard until you copy over it. Copy something else when you're done.
  • While a command runs, decrypted data briefly exists in process memory. Malware already on your machine could read it — true of every password manager; the vault protects the file at rest, not a compromised machine.
  • Single file, no sync. Syncing is your problem (and cp is a fine answer).

Running the checks

./venv/bin/python test_vault.py

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

basic_password_manager-0.2.2.tar.gz (13.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

basic_password_manager-0.2.2-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

Details for the file basic_password_manager-0.2.2.tar.gz.

File metadata

  • Download URL: basic_password_manager-0.2.2.tar.gz
  • Upload date:
  • Size: 13.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for basic_password_manager-0.2.2.tar.gz
Algorithm Hash digest
SHA256 cef066e900bba9456b9585603648b04eda7caac624a2f0d0320e24125c3c81bb
MD5 3712e76fe7686ba8bda445d6f96dd38c
BLAKE2b-256 df3d30a077b031f1ab3e99810e3e584a091122e9bb75858eb84daeadd1db2a2b

See more details on using hashes here.

Provenance

The following attestation bundles were made for basic_password_manager-0.2.2.tar.gz:

Publisher: publish.yml on Daisentaur/password-manager

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file basic_password_manager-0.2.2-py3-none-any.whl.

File metadata

File hashes

Hashes for basic_password_manager-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c236267a6d9d32dbd079ad28092c521b5ca84f3cf8811154b73f935f5ac78214
MD5 2ac22779b1dca9de5d2c4b19c39cd945
BLAKE2b-256 2e2721635cf409bb8cd1f735862a2aabb3bb3be434cb51e2d1b8fbddfa6ff527

See more details on using hashes here.

Provenance

The following attestation bundles were made for basic_password_manager-0.2.2-py3-none-any.whl:

Publisher: publish.yml on Daisentaur/password-manager

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page