Dev.to · 8 min read

Adding account recovery to a zero-knowledge vault without building a back door

Adding account recovery to a zero-knowledge vault without building a back door

Every zero-knowledge password manager ships with the same sentence somewhere in its docs: If you forget your master password, your data cannot be recovered. That sentence is true, and it is not laziness. In a zero-knowledge design the server never receives your key, so when you forget your master password there is genuinely nothing on the company's side to hand back. The alternative — keeping a copy of your key so they can help — is exactly the thing zero-knowledge exists to avoid. It's also a brutal failure mode. One bad memory costs you every account you own, and the blast radius grows every year you use the product. I spent the last ten months building a password manager, and this was the problem I most wanted to solve. Here's the design I landed on, why the obvious approaches don't work, and what it still costs the user. The product is LisaVault — lisavault.com — free to use, and you don't need it to follow any of this. The design is the point of the post. The approaches that don't work Worth walking through these, because each one is what a product reaches for when it wants recovery without thinking hard. Escrow. The provider keeps a copy of your key, or of something that unwraps it. Recovery becomes trivial. So does a subpoena, a rogue employee, and a server breach. This is the thing you're trying not to build. Security questions. These re-derive access from low-entropy answers that are often publicly discoverable. If the answers can unwrap your vault, so can anyone who knows where you went to school. Email-based reset. Click a link, get back in. If that works, the provider had a route into your data all along — your email inbox is now the actual key to your vault, and it's a route they control. Plenty of services work this way legitimately. They just aren't zero-knowledge, whatever the marketing page says. The common failure is that all three make recovery possible using something the provider controls. That's the property to eliminate. The design: don't encrypt the vault with the password The core move is to stop treating the master password as the encryption key. Your vault is encrypted with a random 256-bit key generated on your device at signup. Call it the vault key. Every item is encrypted with it using AES-256-GCM. The vault key never changes for the life of the account. The vault key is then wrapped twice — stored two separate times, each encrypted under a different secret: random 256-bit vault key │ ┌──────────────┴──────────────┐ │ │ wrapped with wrapped with master password key recovery kit key │ │ ▼ ▼ wrapped_key_pw wrapped_key_kit (on server) (on server) Both wrapped copies live on the server. Neither is useful without the corresponding secret, and the server has neither secret. To unlock normally: master password │ ├─ Argon2id (64 MiB, t=3, p=4) ──► master key │ │ │ ├─ HKDF ──► stretched master key ──► unwraps wrapped_key_pw │ │ │ └─ PBKDF2 (1 iteration) ──► authHash ──► sent to server │ └─ never leaves the device The authHash is the only password-derived value the server ever receives, and it's bcrypted again at rest. It can't be reversed into your password or into your key — it exists purely to prove you know the password so the server will hand over the encrypted blobs. For devices that can't spare 64 MiB for Argon2id, there's a PBKDF2 fallback at 600,000 iterations, chosen per account at signup. A password change preserves whatever KDF the account was created with rather than silently upgrading it — silent parameter changes are a great way to lock people out. The Recovery Kit, and the part that took the longest to get right At signup you're shown a Recovery Kit exactly once: 160 random bits, rendered as BS1-XXXXXXXX-... in Crockford base32. Crockford base32 matters more than it sounds. It excludes I, L, O and U — the characters people misread when copying a code by hand off a printed sheet. If your recovery story ends with "type this 30-character string correctly under stress," the alphabet is a real design decision, not a detail. The naive implementation is to derive a key from the kit, use it to wrap the vault key, and let the server check the kit before serving the wrapped blob. But that check is the problem: to verify you hold a valid kit, the server needs something derived from it — and anything strong enough to verify against is usually strong enough to unwrap with. The fix is to split the kit into two halves that do different jobs: recovery kit (160 bits) │ ├─ HKDF ──► key half ──────► wraps/unwraps the vault key │ NEVER leaves the device │ └─ HKDF ──► verifier half ──► sent to server, bcrypted at rest only gates the recovery endpoint The verifier half proves you're holding a kit. It cannot reconstruct the kit, and it cannot unwrap anything. The half that can actually unwrap your vault key never touches the network. And recovery requires both the kit and a code emailed to you. So: The server alone can't recover your vault — it has a verifier and a wrapped blob, and neither opens anything. Your email alone can't recover your vault — the emailed code only satisfies one of two factors. Someone who steals your printed Recovery Kit can't recover your vault without also having your email. There is no combination of things the provider holds that opens a vault. That's the property I wanted, and it's what separates this from escrow. The side effect I didn't plan Because the vault is encrypted with the vault key and not with your password, changing your master password re-encrypts 32 bytes — one wrapper — rather than re-encrypting every item you own. Most vaults re-encrypt everything on a password change. If you have a few thousand items, that's a long, failure-prone operation you'd rather users didn't attempt on flaky mobile data. Here it's a single small write. This wasn't the goal. It fell out of the indirection, which is often how the indirection pays for itself. What this costs, honestly Lose both the master password and the Recovery Kit and the vault is gone. There is no back door for me to open — that's the entire proposition — but the responsibility genuinely sits with the user. Which is why the Kit is hard to skip at signup, and why it's printable. The Recovery Kit is a bearer token. Anyone holding it has one of the two factors. Printed and left in a desk drawer, it's about as secure as the drawer. The emailed code is what stops that alone being enough, which means your email account's security is now load-bearing — say so plainly rather than hoping nobody works it out. Unlocking is deliberately slow. Argon2id at 64 MiB takes around half a second, longer on an old phone. It could be instant. It would also be far easier to attack offline. A compromised browser is a compromised vault. While your vault is unlocked, the decryption key is in memory in your browser. Malware, or an extension with broad page permissions, can read it. No web-based password manager avoids this — anyone claiming otherwise is selling something. Two related decisions While I'm here, two other choices that follow the same logic. Items are stored as one encrypted blob, not field by field. Several managers encrypt each field separately, which means their servers can see item names and URIs — enough to enumerate which services you hold accounts with. One blob per item means my server can't produce that list, even under legal compulsion. What it costs me: no server-side search and no field-level sync. Search and vault health checks run client-side, deliberately, because a server-side version would reveal which accounts you look up. Breach checking uses HIBP k-anonymity. SHA-1 is computed locally and only the first five hex characters go over the wire. On Android it goes direct to Have I Been Pwned so my server isn't in the loop at all. Where this actually is I've been building this solo since April. It's called LisaVault, it's live, and it's free to start: 👉 lisavault.com The free tier includes the TOTP authenticator, vault health reports, breach checking and a security activity log — the four things most managers put behind a paywall — plus unlimited items, import from Bitwarden, LastPass, Chrome and 1Password, and encrypted export. What it does not have, so nobody finds out the hard way: no independent security audit yet, the client isn't open source yet, no iOS app. The browser extension is built and in review with the Chrome Web Store. Android is feature-complete and in closed testing. All of that is stated on the public security page rather than buried. What I'd like from you If you work with cryptography, I'd genuinely like the recovery design attacked. It's the part where I've done something different from everyone else in the category, which makes it the part most likely to be wrong in a way I can't see from the inside. The specific questions I keep turning over: Is the HKDF split sound, or does the verifier half leak more than I think under some access pattern? Is requiring the emailed code as a second factor buying real security, or is it security theatre given the kit is already high-entropy? Is there an ordering or timing attack on the recovery endpoint I've missed? Tell me in the comments. I'd rather hear it now than after a few thousand people have vaults in it. And if you'd rather just try it and tell me what breaks, that works too — lisavault.com. Free tier, no card, import from whatever you're using now. — Abhay, building LisaVault

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Cybersecurity News