For years my browser has known every password I own. Which always felt a little off. My most sensitive secrets, living inside the most attacked program on my computer, synced to a cloud I don't control, guarded by whoever is logged into my firefox. I wanted them somewhere that was mine: one encrypted file, on my disk, openable with one word that only exists in my head and maybe a secret notebook(super secret info).
I also wanted to know how a password manager actually works. What's inside the vault? Where does the master password go? What happens, mechanically, when you type it wrong? Turns out the answers are smaller and prettier than I expected, and the whole thing fits in about 120 lines of Python.
But first I have to admit to the detour.
The Rust detour I produced seven lines on
The original plan was grander: this would also be the project where
I finally learn Rust. Two birds, one repo. I installed the
toolchain, made the folders, wrote fn derive_key() {}
,an empty function with a promising name and then
did not touch it again for days. Total output: seven lines, most of
them punctuation.
It wasn't Rust's fault (only partially). It's that I'd stacked two to-do's on top of each other a new language and a domain I didn't understand. Meanwhile the actual goal, the one with a point to it, was "get my passwords out of the browser," and that goal didn't care what language it happened in.
A password manager is three small ideas in a trench coat
Here's what I actually learned, and honestly the reason I'm writing this up. Strip away the apps and the subscriptions and a password manager is three ideas:
One: turn a word into a key, slowly, on purpose. Encryption wants a key: 32 bytes of pure randomness. My master password is not that. A thing called Argon2 bridges the gap. It chews on the password and always spits out the same 32 bytes. The clever part is that it's engineered to be slow and memory-hungry: every single guess costs real time and a big chunk of RAM. The pause when I unlock my vault? That's not my code being sluggish and unoptimised (maybe a little). That's the lock being hard to pick, for me and for anyone who steals the file.
Two: encryption that talks back instead of staying quiet. The vault is sealed with something called authenticated encryption. Feed it the wrong password or a file where even a single bit was flipped, by corruption or by tampering, and it doesn't hand back garbage that looks plausible. My test for this literally flips one byte in the file and checks that the vault says. There is no silent failure mode, which for a box full of passwords is the only acceptable personality.
Three: one file. That's the whole storage layer.
My passwords are a small list, the list gets encrypted, the result
is one file on disk. No database, no service, no sync engine.
Backing it up is cp.
And I should be honest the same way I always am: I didn't write the cryptography. Smarter people spent decades on Argon2 and AES so that I could call two library functions. My 120 lines are the tape around them. The file format, the commands, the decisions. As always, the best code is the code I didn't write.
The word that is stored nowhere
Extremly Importantly. My master password is not saved anywhere. Not hashed, not hidden, not encrypted absolutely nowhere. It exists in my head and, for a few seconds while I type it, in memory. So how does the vault know when I typo it? It technically doesn't. It derives a key from whatever I typed, tries to decrypt, and the decryption itself screams if the key is wrong. "Wrong password" is just the scream, translated.
Which has a consequence worth sitting with: there is no reset. No "forgot password?" link, no support line, nobody to appeal to. If I forget that one word, my vault becomes what it already is to everyone else on earth — noise. This sounds like a flaw until you flip it around: every "forgot password" flow is a back door, and back doors don't check IDs. The absence of mercy is the security. I picked a long phrase I can't forget, and I mean that in the way you don't forget a sentence, not the way you memorize a string of symbols.
Shipping it like a real boy
Then I did something I'd never done: put it on PyPI, the place
pip install pulls from. Turns out publishing a Python
package for the whole world is one config file and two commands.
My first-choice name was taken, so it lives as
basic-password-manager — honest advertising ;p
The terminal app that ignored its own themes
Version two grew a proper interface. Not a window but a terminal app, the kind with panels and keyboard-driven everything. I shamelessly copied the look of tuxedo, a todo app whose muted slate palette I'd been admiring, by reading its theme file and lifting the exact colors. I also love the tool itself and use it myself.
Then I noticed something: the app had a theme switcher buried in a menu, and picking themes changed… only the menu. The entire rest of the screen stayed stubbornly slate. The bug was my admiration: I'd hardcoded tuxedo's colors into every surface, which meant no theme could ever touch them. The framework underneath had a whole theming system, which I obviouslt didn't read my first time stealing their deisgn choices. You register your palette, you paint with variables, everything re-colors itself live — and I had painted over it like wallpaper over a window and was thinking why the weather was so consistent.
Where this goes next
The obvious hole: my passwords live on my laptop and my logins happen, half the time, on my phone. The next version scans a QR code off my terminal and hands my phone the encrypted vault over my home WiFi — still ciphertext in transit, decrypted only on the phone, with the master password typed there and nothing saved behind. Because the thing this project actually taught me is that the file was never the fragile part. The encrypted blob can travel anywhere and stay noise. What you guard is the endpoints — the places where the one word in your head touches a keyboard.
In the meantime: my browser has forgotten every password it ever knew, and a small unreadable file in my home folder knows them all. I open it twenty times a day with one word and a one-second pause. And man do I love why that pause is there.