case study
wePass
A zero-knowledge password manager that encrypts every entry in the browser
overview
wePass is a personal password manager where credentials are encrypted in the browser before they're uploaded, so the server stores only ciphertext. I built it with Next.js, the Web Crypto API, Prisma and PostgreSQL, with a separate vault PIN that has to be entered before any password is revealed or copied.
problem
If a password manager encrypts on the server with one global key, a server breach exposes every user's passwords. I wanted a design where the database on its own is useless to an attacker, because the key needed to decrypt it never reaches the server.
It also needed a second barrier for the case where someone gets to an unlocked session on your machine.
approach
- 01On login or signup the browser derives a 256-bit AES key from the account password using PBKDF2-SHA256 with 600,000 iterations and a 32-byte per-user salt, all through the Web Crypto API.
- 02Each credential is encrypted with AES-256-GCM using a fresh 96-bit IV, and only the ciphertext and IV are sent to the API and stored in PostgreSQL via Prisma. There is no server-side decryption path for new entries.
- 03The derived key lives in sessionStorage and is cleared on logout or when the tab closes, so the next visit requires logging in again to re-derive it.
- 04A 4-digit vault PIN, stored as a bcrypt hash (cost 12), gates every reveal and copy. PIN checks are rate-limited to 5 attempts per 15 minutes, login to 10, signup to 5 per hour and salt lookups to 20.
- 05Login always runs a bcrypt compare, against a dummy hash when the email doesn't exist, and the salt lookup always returns 200, so neither reveals whether an account exists. Auth is a JWT in an httpOnly cookie, and security headers (HSTS, X-Frame-Options DENY, Permissions-Policy) are set on every response.
features
Client-side encryption
AES-256-GCM with a unique IV per entry, encrypted before upload.
PBKDF2 key derivation
600,000 SHA-256 iterations with a per-user salt, matching the OWASP 2023 recommendation.
Vault PIN
A separate bcrypt-hashed PIN is required to reveal or copy any password.
Rate limits and timing-safe login
Per-action attempt limits and a constant bcrypt compare prevent brute force and account enumeration.
Categories and search
Entries are grouped into categories such as Social, Work and Banking, with live search and per-category counts.
challenges
Where the key lives
The key has to survive page reloads but must never reach the server. I export it to sessionStorage, which is scoped to the tab and cleared when it closes, and wipe it on logout.
Not leaking which emails exist
The client needs the user's salt before it can derive the key, which could reveal registered emails. The salt lookup always responds 200 and is rate-limited, and login runs bcrypt even for unknown emails so timing stays the same.
outcome
wePass is live at wepass.vercel.app with signup, an encrypted vault, the PIN gate and categories. The README lists next steps: a password generator, editing entries, idle auto-lock and encrypted export.
stack
contact
Have something worth building?
Open to freelance. Fastest response on WhatsApp ↗ — usually within the hour.