Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Password Manager

A zero-trust password manager for your browser. Never remember more than your master password. All account data is encrypted in the browser using AES-GCM with a PBKDF2-derived key and stored as encrypted files on any PHP server. No database required. No trust in the server's provider required.

No dependencies. No frameworks. No tracking. Just 42KB of vanilla JavaScript.

Works in Firefox, Chrome, Safari and Edge on desktop and mobile.

Features

  • One password to open them all: master password opens a complete password list at once
  • Unlimited password lists: each master password opens its own independent account set
  • Encrypted table view: searchable, sortable, one-click copy to clipboard
  • Category / subcategory tree: click to filter, fully configurable with custom emojis
  • New accounts: password generator with selectable complexity (7 character pools including emojis)
  • TOTP support: RFC 6238 / RFC 4226 implementation, live token display (30-second countdown)
  • Change master password: re-encrypts all accounts, keeps old data safe
  • Edit configuration: categories, subcategories, password defaults, character sources
  • Mobile responsive: collapsible detail rows on screens under 600px
  • Inactivity logout: configurable countdown timer
  • automated dark mode: well, we have that too

Security Architecture

  • Master Password: The only secret you need to remember. Never leaves the browser in plain text.
  • Server Pepper: A cryptographically random 32-byte pepper stored in pepper.json on the server. Appended to passwords before hashing, preventing rainbow table attacks without exposing the pepper.
  • Per-User Realm: Each master password gets a unique 32-byte random value prepended to the password before encryption, ensuring identical passwords produce different ciphertext.
  • PBKDF2 Key Derivation: Master password and per-user realm derive AES-256-GCM keys via 250,000 iterations of PBKDF2 with SHA-256.
  • Random Salt + IV: Every encryption operation uses a fresh 16-byte salt and 12-byte IV. These are prepended to the ciphertext (base64-encoded) - the server never sees unencrypted data.
  • Session Key: The master password is encrypted with a random session password on page load. Cleared on inactivity logout.
  • AES-GCM: Authenticated encryption. Tampered ciphertext decrypts to empty string automatically.
  • Content Security Policy: Inline meta CSP restricts script and style sources to self only.
  • Anti-Spoofing: Old encrypted data files are never deleted. When the browser uploads new data, the server first checks if there was a .pwd file uploaded within the last 5 minutes. If so, it overwrites that file; otherwise it creates a new timestamped file. This protects against a few things:
    • If an attacker injects a request, they can only overwrite the single most recent .pwd file - the previous version is safe.
    • The 5-minute window is not about limiting how often you can save. It is about preventing the server disk from filling up if a user clicks "Save" seven times in a row on a bad connection.
  • Local Time check: TOTP relies on the device clock. A built-in check informes you if your device time is off.

Data Flow

Login & Load Data

The user enters their master password. The browser hashes it (with pepper) and sends only the hash to the server. The server looks up the matching data directory and returns both the config and the encrypted data. Everything is decrypted in the browser.

sequenceDiagram
    participant Browser
    participant Server

    Browser->>Server: GET ajax.php?do=getPepper
    Server-->>Browser: {pepper: "..."}
    note right of Browser: Pepper is public, prevents rainbow tables

    Browser->>Browser: hash = sha256(masterPassword + pepper)

    Browser->>Server: POST ajax.php?do=login&hash=<hash>
    note left of Server: Server sees only the hash.<br/>It cannot derive the master password.

    Server->>Server: Find /private/<hash>/
    Server->>Server: Read newest config*.json
    Server->>Server: Read newest *.pwd
    Server-->>Browser: {config: {...}, data: "encrypted..."}

    Browser->>Browser: decrypt(data, masterPassword)
    Browser->>Browser: Parse JSON, render table & tree
Loading

Store Account Data

The user clicks Save. The browser encrypts the data and uploads it. The server checks if a .pwd file was uploaded within the last 5 minutes - if so, it overwrites that file; otherwise it creates a new one. This is the anti-spoofing mechanism described above.

sequenceDiagram
    participant Browser
    participant Server

    Browser->>Browser: encrypt(JSON.stringify(data), masterPassword)
    note right of Browser: AES-256-GCM with random<br/>salt (16B) + IV (12B)

    Browser->>Server: POST ajax.php?do=store&hash=<hash>&data=...
    note left of Server: Server stores file by timestamp.<br/>Cannot decrypt, cannot read.

    Server->>Server: Check: was there a *.pwd<br/>uploaded within 5 minutes?
    alt yes
        Server-->>Browser: overwrite existing
    else no
        Server->>Server: Save as /private/<hash>/yyMMdd_HHmm.pwd
    end
    Server-->>Browser: ok
Loading

Change Master Password

The user enters their current and new master password. The browser verifies the current password, then creates a new data directory under the new hash. The browser re-encrypts all accounts with the new password and uploads the data. The old directory remains intact - the old master password still works until the user deletes it from the config.

sequenceDiagram
    participant Browser
    participant Server

    Browser->>Browser: Verify current master password
    Browser->>Browser: sha256(newMasterPassword + pepper)

    Browser->>Server: POST ajax.php?do=changeMasterPassword&hash=<oldHash>&newHash=<newHash>
    note left of Server: Server creates new directory<br/>under the new hash

    Server->>Server: mkdir /private/<newHash>/
    Server->>Server: Create config.json with new masterRealm
    Server-->>Browser: {ok: 1, masterRealm: "..."}

    Browser->>Browser: Re-encrypt all accounts with new password
    Browser->>Server: POST ajax.php?do=store&hash=<newHash>&data=...
    Server->>Server: Save as /private/<newHash>/*.pwd

    Note over Browser,Server: Old directory <masterHash1> remains intact.<br/>Old master password still works until user deletes it.
Loading

Save Configuration

The gear icon (⚙️) opens the config editor. The user edits categories, subcategories, password defaults, and character sources. On save, the config is uploaded as a timestamped JSON snapshot. On login, the server returns the newest config*.json.

sequenceDiagram
    participant Browser
    participant Server

    Browser->>Browser: Collect categories, subcategories,<br/>password defaults, character sources

    Browser->>Server: POST ajax.php?do=saveConfig&hash=<hash>&config=JSON
    Server->>Server: Save as /private/<hash>/config_yyMMdd_HHmm.json
    Server-->>Browser: ok

    Note over Browser: Next login will automatically<br/>read the newest config*.json
Loading

Config fields:

Key Type Description
masterRealm string Per-user salt for encryption key derivation
categories object {name: emoji} mappings
subCategories object {name: emoji} mappings
showSubCategorieIconsInTreeview boolean Show subcategory emojis in tree
passwordSuggestionLength number Default password length (8–64)
pageInactivityTimeout number Seconds until auto-logout (30–3600)
passwordSuggestionDefaults object Default complexity checkboxes
passwordSuggestionSources object Character pools for generation

Screenshots

Login

desktop mobile
Login on desktop in light mode Login on mobile in dark mode

Account table

desktop mobile
account list with category tree on desktop in light mode account list on mobile in light mode
account list with category tree on desktop in dark mode account list on mobile in dark mode

Create or change account

desktop mobile
account form on desktop in light mode account form on mobile in light mode
account form on desktop in dark mode account form on mobile in dark mode

Change config

change config

Change master password

change master password

Installation

  1. Copy everything to a server that supports PHP (version 5.6+).
  2. Point your server configuration to the public/ directory. Never expose the root folder of this project.
  3. Make sure the private/ directory is writable by the web server user (e.g. chmod 755 private/).
  4. Open your browser and log in with a new master password. Open the browser console (F12) and copy the shown hash.
  5. Create a directory in private/ named after this hash.
  6. Login again. Done.

Upgrade

If you are upgrading from a version that still uses config.php instead of per-user JSON files:

  1. make a backup of the /private folder
  2. make folder /public empty but keep private
  3. Copy everything to a server that supports PHP (version 5.6+).
  4. Open your browser and log in with your master password.
  5. The migration will automatically:
  • Create a backup zip of all existing files in private/.
  • Convert config.php into per-user JSON files.
  • Create pepper.json with a random pepper.
  • Delete itself and the old config.php.

After a successful login, migrate_to_json.php will have already deleted itself. If it has not, you can delete it manually.

File Structure

PasswordManager/
├── private/
│   ├── pepper.json                    # Server-wide pepper (auto-created)
│   ├── migrate_to_json.php            # Migration tool (auto-deletes after use)
│   ├── config-template.json           # Template for new master password dirs
│   ├── <masterHash1>/
│   │   ├── config_261224_2356.json    # Newest config for that hash, will be used when logging in
│   │   ├── config_190124_1455.json    # Older config snapshot
│   │   ├── 270117_1234.pwd            # Newest encrypted account data, will be used when logging in
│   │   ├── 270116_2157.pwd            # Older encrypted account data
│   │   └── 261217_1548.pwd            # Older encrypted account data
│   └── <masterHash2>/
│       ├── config_yyMMdd_HHmm.json    # Timestamped config snapshots
│       └── yyMMdd_HHmm.pwd            # Encrypted account data
├── public/
│   ├── index.html                     # Single-page UI
│   ├── scripts.js                     # All client-side logic
│   ├── styles.css                     # Responsive styles
│   └── ajax.php                       # Server-side endpoint
└── readme.md                          # This file

After changing the master password, a second directory is created:

├── private/
│   └── <masterHash2>/                 # New master password directory
│       ├── config_yyMMdd_HHmm.json    # Timestamped config snapshots
│       └── yyMMdd_HHmm.pwd            # Re-encrypted account data

Technology

Layer Technology
Frontend Vanilla JavaScript (ES6+), no dependencies
Encryption Web Crypto API - AES-256-GCM, PBKDF2 (250k iterations), SHA-256
TOTP RFC 6238 / RFC 4226 native implementation
Backend PHP 5.6+ (single ajax.php endpoint)
Storage Text files with encrypted content (.pwd), JSON configs - no database
Transport HTTPS recommended

License

MIT

About

Manage your passwords in the browser

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages