work

case study

wePass

A zero-knowledge password manager that encrypts every entry in the browser

Solo — design & full-stack · May 2026 · personal

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

Next.js 16 (App Router)React 19TypeScriptWeb Crypto API (PBKDF2, AES-256-GCM)PrismaPostgreSQL (Neon)TanStack QueryReact Hook FormZodjsonwebtoken / josebcryptjsTailwind CSS v4Framer Motion

contact

Have something worth building?

Open to freelance. Fastest response on WhatsApp ↗ — usually within the hour.

tayyabkhangk4734@gmail.com

next project

FarazPsd Portfolio