A self-hosted, zero-knowledge password manager for your home network. Built with Flask, React (Material UI), and a Chrome extension. Your master password and vault data never leave your browser unencrypted — the server only ever stores opaque ciphertext.
Originally created by Hood Informatik (Windkey) — continued and finished by MilcioSSQ.
- Zero-knowledge encryption — Argon2id key derivation + AES-256-GCM, entirely client-side. The server never sees your master password or decrypted vault.
- 2FA (TOTP) — Every account requires Google Authenticator (or compatible). QR code setup built in.
- Password generator — Configurable length, character sets, and rules.
- Password health check — Strength analysis for all stored passwords at a glance.
- Breach scanner — Checks your passwords against HaveIBeenPwned using k-anonymity (never sends your actual password anywhere). Checking an email address for known breaches is also supported, but needs your own HIBP API key — see Configure the backend below.
- Duplicate detection — Flags reused passwords across entries.
- Categories — Organize passwords into custom groups.
- Activity history — Logs every vault action with timestamps and IP.
- Recovery key — One-time key generated at registration. The only way to change your master password without losing data. No "forgot password" email reset — by design.
- Import / Export — Move your data in and out.
- Chrome extension — Autofill credentials in the browser, connected to your self-hosted server.
- Dark / Light mode — Full theme support.
- Responsive UI — Works on desktop, tablet, and mobile.
- LAN-only — The backend rejects any request from outside your local network.
| Layer | What it does |
|---|---|
| Argon2id | Derives your vault key from your master password in the browser (memory-hard, anti-brute-force). |
| AES-256-GCM | Encrypts every vault entry client-side before it hits the server. |
| CSRF protection | Flask-WTF synchronizer tokens on all state-changing requests. |
| Rate limiting | Flask-Limiter on login, register, and recovery endpoints. |
| Account lockout | 10 failed login attempts → 15 min lockout. |
| HTTPS | Self-signed dev certificate for WebCrypto API access on LAN IPs. |
| Enumeration protection | Pre-login responses are deterministic per email — no user-exists oracle. |
Backend: Python 3.11+, Flask, SQLAlchemy (SQLite), Flask-Login, Flask-Limiter, PyOTP, bcrypt
Frontend: React, Material UI, Axios, react-router-dom
Crypto: Argon2id + AES-256-GCM + HKDF (via noble-hashes, runs entirely client-side)
Chrome Extension: Manifest V3, content scripts + popup + options page
winkey/
├── backend/
│ ├── app.py # Flask app, DB models, LAN-only middleware
│ ├── routes.py # API endpoints (auth, vault, recovery, history, breach-check)
│ ├── mail.py # Verification & recovery emails
│ └── generate_dev_cert.py # Self-signed HTTPS cert generator
├── frontend/
│ └── src/
│ ├── components/
│ │ ├── Dashboard.js # Main vault view (add/edit/delete/search)
│ │ ├── Login.js # Login + 2FA
│ │ ├── Register.js # Registration + TOTP setup
│ │ ├── Recovery.js # Master password recovery via recovery key
│ │ ├── Stats.js # Password health, duplicates, breach check
│ │ ├── Tools.js # Generator, health check, breach scanner, import/export
│ │ ├── PasswordGeneratorTool.js / Authenticator.js / ImportExport.js / BreachScanner.js
│ │ ├── History.js # Activity log
│ │ ├── Layout.js # App shell / navigation
│ │ ├── Unlock.js # Session unlock
│ │ └── VerifyEmail.js # Email verification flow
│ ├── contexts/
│ │ ├── AuthContext.js # Auth state, session, vault key
│ │ └── ThemeContext.js # Dark/light mode
│ └── crypto/
│ └── windkeyCrypto.js # Argon2id + AES-GCM + HKDF — the crypto core
├── chrome-extension/ # MV3 extension with autofill
│ ├── manifest.json
│ ├── popup.html / popup.js
│ ├── options.html / options.js
│ ├── content.js / content.css
│ ├── background.js
│ └── vendor/ # Bundled noble-hashes for offline crypto
└── requirements.txt
- Python 3.11+
- Node.js 18+ and npm
- A Gmail account with an App Password (optional — without it, verification emails are printed to the console)
- Optional: a HaveIBeenPwned API key (paid, ~3.50 USD/month) if you want the email breach-check to work — the password breach-check is free and works without it
cd backend
pip install -r ../requirements.txt
python generate_dev_cert.pyEdit backend/.env:
SECRET_KEY= # leave blank to auto-generate on first run
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-app-specific-password
FRONTEND_URL=https://<your-lan-ip>:3000
ADMIN_EMAIL=your-email@example.com # the only address that can register without an invite code
HIBP_API_KEY= # optional - only needed for the email breach-check in Tools > Datenleck ScannerWithout HIBP_API_KEY, the breach scanner's password check still works fully (it's free and never needs a key) — only the email check will show a "not configured" message until you add one.
cd backend
python app.pyRuns on https://0.0.0.0:5000.
cd frontend
npm install
npm startOpens at https://localhost:3000. Accept the self-signed certificate warning on first visit — expected and safe.
chrome://extensions/→ enable Developer mode- Load unpacked → select the
chrome-extensionfolder - Open the extension options and set your server address
- Recovery key lost + master password forgotten = permanent data loss. This is inherent to zero-knowledge encryption, not a bug.
- Master password rules are client-side only — the server never sees the raw password.
- Screenshot prevention is best-effort — revealed passwords auto-hide on tab blur, but OS-level screenshots cannot be blocked.
- This is a hardened personal project, not an audited enterprise product.
Originally created by Hood Informatik as Windkey. The initial version was built live on stream — I (MilcioSSQ) picked it up, finished the remaining features, and maintain this version.
MIT — see LICENSE.






