Files
PassKeeper/README.md
T
2026-04-26 10:10:29 -04:00

10 KiB

PassKeeper 🔐

A self-hosted, zero-knowledge password manager — web app and browser extension. Modelled after LastPass, built on Flask + MySQL + Web Crypto API.

Your master password and decrypted vault data never leave your browser. The server stores only encrypted blobs it cannot read.


Features

Web App

  • Zero-knowledge encryption — AES-256-GCM client-side, 600k-iteration PBKDF2 vault key derivation
  • 7 item types — Passwords, Secure Notes, Payment Cards, Bank Accounts, Addresses, Identities, Passkeys
  • TOTP/2FA support — per-site TOTP codes stored inside the encrypted vault blob; live 6-digit display with countdown
  • Folder organisation — create, rename, delete folders; filter vault by folder
  • Item sharing — zero-knowledge ECDH P-256 re-encryption; share with any registered user
  • Emergency access — configurable wait-timer access grant for a trusted contact
  • Security dashboard — weak, reused, and old password detection + HaveIBeenPwned k-anonymity breach check (passwords never transmitted)
  • Account MFA — TOTP-based login verification (Google Authenticator / Authy)
  • Master password change — atomic zero-knowledge re-encryption of the entire vault
  • Account recovery — 128-bit recovery code; server never stores it
  • Audit log — server-side trail of all create/edit/delete actions; no sensitive data ever logged
  • Encrypted item names — item names stored as AES-256-GCM ciphertext; server holds only the item type as a label
  • Responsive layout — phone, tablet, laptop, and large desktop breakpoints; collapsible sidebar

Browser Extension (Chrome / Edge — Manifest V3)

  • Autofill — detects login forms; injects icon into username and password fields only (scored heuristic, not all text inputs)
  • Suggestion dropdown — anchored below the focused field; filters as you type; keyboard navigation (↑↓ Enter); fills with one click
  • Smart domain matching — matches vault items by domain name, handles bare domains (github.com) and subdomains
  • Auto-save banner — prompts to save or update credentials on form submit; duplicate detection; "Never for this site" blocklist
  • Inline password generator — length slider, charset toggles, strength indicator; fully CSPRNG (crypto.getRandomValues throughout)
  • TOTP live display — 6-digit code + countdown timer per item in the popup
  • SSO bridge — log in once on the web app; extension picks up the session automatically
  • Idle lock — configurable auto-lock timeout (1 / 5 / 10 / 30 min / Never)
  • Security — decrypted vault data stored in chrome.storage.session only (memory-only, never written to disk)

Security Architecture

Master Password
      │
      ├─ PBKDF2(email, 100k iter)  ──►  authHash  ──►  POST /api/auth/login
      │                                                       │
      │                                              Argon2id(authHash) stored in DB
      │
      └─ PBKDF2(enc_key_salt, 600k iter)  ──►  vaultKey (stays in browser memory only)
                                                      │
                                              AES-256-GCM encrypt
                                                      │
                                              enc_data + iv  ──►  POST /api/vault
                                              enc_name + iv_name  (item name, encrypted separately)

The server is blind to all vault content. A database breach exposes only encrypted ciphertext.


Tech Stack

Layer Technology
Backend Python 3.12, Flask 3.x
Database MySQL 8.x
Frontend Vanilla JS, Web Crypto API, Jinja2
Auth Argon2id + PBKDF2 + JWT (HS256)
Encryption AES-256-GCM (client-side)
Extension Chrome Manifest V3
Web server Nginx + Gunicorn + systemd
Rate limiting Flask-Limiter + Redis
TLS Let's Encrypt / Certbot

Getting Started

Prerequisites

  • Python 3.12+
  • MySQL 8.x
  • Node.js is not required — no build step

Development (Windows)

# 1. Clone and set up virtual environment
git clone https://github.com/yourname/passkeeper
cd passkeeper
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt

# 2. Configure environment
copy .env.example .env

Edit .env:

FLASK_ENV=development
SECRET_KEY=your-secret-key
JWT_SECRET_KEY=your-jwt-secret
MYSQL_HOST=localhost
MYSQL_USER=passkeeper
MYSQL_PASSWORD=yourpassword
MYSQL_DB=passkeeper
TOTP_ENCRYPTION_KEY=64-char-hex-string   # python -c "import secrets; print(secrets.token_hex(32))"
# 3. Create database
mysql -u root -p -e "CREATE DATABASE passkeeper CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -u root -p -e "CREATE USER 'passkeeper'@'localhost' IDENTIFIED BY 'yourpassword';"
mysql -u root -p -e "GRANT ALL PRIVILEGES ON passkeeper.* TO 'passkeeper'@'localhost'; FLUSH PRIVILEGES;"

# 4. Create tables
python reset_db.py

# 5. Run
python run.py

Open http://localhost:5000.

Load the Extension (Chrome)

  1. Open chrome://extensions/
  2. Enable Developer mode (top-right toggle)
  3. Click Load unpacked → select the extension/ folder
  4. Reload the extension after any JS/CSS changes

Content script logs appear in the page's DevTools console. Background service worker logs are at chrome://extensions/ → PassKeeper → "Service Worker".


Production Deployment

1. Server dependencies

sudo apt update && sudo apt install python3.12 python3.12-venv mysql-server nginx \
    certbot python3-certbot-nginx redis-server

2. Application setup

cd /var/www/passkeeper
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env   # set production values
flask db upgrade       # run all migrations

3. systemd service

Copy scripts/passkeeper.service to /etc/systemd/system/passkeeper.service, then:

sudo systemctl daemon-reload
sudo systemctl enable --now passkeeper
journalctl -xeu passkeeper.service

Key flags in the service unit: --preload (single app import, lower memory), WatchdogSec=60s, Restart=on-failure.

4. Nginx

Copy scripts/passkeeper-nginx.conf to /etc/nginx/sites-available/passkeeper, update server_name, then:

sudo ln -s /etc/nginx/sites-available/passkeeper /etc/nginx/sites-enabled/
sudo certbot --nginx -d yourdomain.com
sudo nginx -t && sudo systemctl reload nginx

The Nginx config uses an upstream block with proxy_next_upstream for zero-downtime Gunicorn restarts, dual rate-limit zones (auth endpoints and general API), and Cache-Control: immutable for static assets.

5. Schema migrations (future updates)

flask db upgrade
sudo systemctl reload passkeeper

Environment Variables

Variable Description Example
SECRET_KEY Flask session secret 64-char random hex
JWT_SECRET_KEY JWT signing secret 64-char random hex
MYSQL_HOST MySQL host localhost
MYSQL_USER MySQL username passkeeper
MYSQL_PASSWORD MySQL password
MYSQL_DB Database name passkeeper
TOTP_ENCRYPTION_KEY Server-side AES key for TOTP secrets 64-char hex
RATELIMIT_STORAGE_URI Redis URI for rate limiting redis://127.0.0.1:6379/0
CORS_ORIGINS Allowed CORS origins https://yourdomain.com

Generate secrets:

python -c "import secrets; print(secrets.token_hex(32))"

Project Structure

passkeeper/
├── app/                    # Flask application
│   ├── models/             # SQLAlchemy models
│   ├── routes/             # API blueprints (auth, vault, folders, sharing, emergency)
│   ├── services/           # Auth service (Argon2id, JWT, TOTP encryption)
│   ├── static/js/          # Client-side crypto + vault UI
│   └── templates/          # Jinja2 HTML templates
├── extension/              # Chrome extension (Manifest V3)
│   ├── popup/              # Popup UI (HTML + CSS + JS)
│   ├── content/            # Content script (form detection, autofill icon, dropdown)
│   ├── shared/             # Shared crypto (vault key, encryptName/decryptName)
│   ├── bridge/             # SSO bridge (web app ↔ extension session sync)
│   └── background.js       # Service worker (badges, message relay, idle lock)
├── migrations/             # Alembic migration scripts
├── scripts/                # Nginx config, systemd unit, backup scripts
├── reset_db.py             # Dev-only: drop + recreate all tables
├── requirements.txt
├── run.py                  # Development server entry point
└── wsgi.py                 # Gunicorn entry point

API Overview

All vault and folder endpoints require Authorization: Bearer <access_token>.

Method Endpoint Description
POST /api/auth/register Create account
POST /api/auth/login Authenticate; returns tokens or MFA challenge
POST /api/auth/mfa/verify Complete MFA step
POST /api/auth/refresh Rotate refresh token
POST /api/auth/logout Blacklist both tokens
GET /api/vault List all encrypted vault items
POST /api/vault Create item (enc_data, iv, enc_name, iv_name)
PUT /api/vault/<id> Update item
DELETE /api/vault/<id> Delete item
GET /api/folders List folders
POST /api/sharing Share item (ECDH re-encryption)
POST /api/emergency Create emergency access grant
POST /api/auth/change-password Atomic vault re-encryption on password change
POST /api/auth/recover Account recovery (one-time use)

Backup

Automated MySQL backups with 30-day retention:

# Install cron job
sudo cp scripts/passkeeper-logrotate /etc/logrotate.d/passkeeper
crontab scripts/backup.cron

Manual backup:

bash scripts/backup_db.sh

Licence

MIT — see LICENSE.