Security
Every password manager says it takes security seriously. This page gives you the specifics instead: the exact algorithms, the exact parameters, and a complete account of what we hold. Judge KalmPass on what it does rather than on what it claims.
Your vault is encrypted in your browser before anything is sent. The key comes from your master password, which never leaves your device. We store ciphertext, and we store it encrypted a second time under a key kept outside the database. There is no mechanism by which KalmPass can read your items, and no support process that can restore them for you.
What protects your data
1. Turning a password into a key
Your master password is stretched with PBKDF2-HMAC-SHA256 at 1,000,000 iterations, salted with your email address. OWASP's current floor for PBKDF2-SHA256 is 600,000; we sit comfortably above it. Salting with the address means two people who choose the same password still derive different keys, and rules out precomputed tables.
The result is split by HKDF-SHA256 into two independent branches: an encryption key that stays in the browser tab, and an auth key that is sent to the server to prove who you are. Because HKDF outputs for different labels are independent, holding the auth key reveals nothing about the encryption key. A fully compromised server still cannot decrypt a single item.
2. Encrypting the vault
Items are encrypted with AES-256-GCM under a random vault key, with a fresh 96-bit IV drawn from the CSPRNG for every single encryption. Item names, usernames, URLs and notes are all inside the encrypted blob, so the database cannot be mined for which sites you hold credentials for. Plaintext is padded to a 256-byte boundary first, so the stored length does not disclose how long a note or a password is.
The vault key itself is random and stable. Your master password wraps it rather than encrypting items directly, which is why changing your password takes a moment instead of re-encrypting your whole vault, and why your Recovery Key keeps working afterwards.
3. A second layer on our side
Everything the client sends is encrypted again by our server under a key derived from a Cloudflare Worker secret that is never stored in the database. The ciphertext is bound to its row with AES-GCM additional authenticated data, so a blob cannot be lifted out of one record and replayed into another.
This layer is not what keeps us out. The client layer already does that. It is there so that a leaked database backup, a stolen storage credential, or a subpoena served on the storage layer alone yields nothing readable at all.
4. Even the email address
Account addresses are stored as a keyed HMAC blind index for lookup, plus an encrypted copy. The database holds no readable identifier, so it cannot be mined for a customer list or cross-referenced against a breach corpus.
Signing in
| Password verifier |
The server stores HMAC(pepper, PBKDF2(auth_key, salt, 100k)). The pepper
lives in a Worker secret, not the database, so a stolen database cannot be cracked
offline at all.
|
|---|---|
| Sessions | 256-bit random tokens in an HttpOnly, Secure, SameSite=Strict cookie. Only the HMAC of the token is stored, so the session table cannot be used to mint a login. Sessions expire after 30 minutes idle and 12 hours absolute, whichever comes first. |
| Two-factor | Optional TOTP (RFC 6238) on the login itself, verified in constant time across the accepted clock-drift window. Ten single-use backup codes are issued, stored only as peppered hashes. |
| Rate limiting | Failures are counted by email and by IP separately, with exponential lockout, so a flood from one address cannot lock a different account out. Counters are keyed by blind index. |
| Enumeration | Login spends identical work whether or not the address exists, and always returns the same error. Account reset requests always answer the same way. |
| CSRF | SameSite=Strict on the session cookie, plus a strict Origin check on every state-changing request. |
| The page itself |
A Content-Security-Policy with no inline script and no external origins at all, plus
HSTS, X-Frame-Options: DENY, and no-store on everything. The
app loads nothing from a CDN, because a CDN is a place someone else can change your
crypto.
|
Recovery, honestly
Zero-knowledge encryption and "email me a password reset" cannot both be true. If a server can restore your vault from an email link, then that server can read your vault, and so can anyone who breaks into it.
KalmPass resolves this the way an emergency kit does. At signup you are issued a Recovery Key: 125 bits of randomness, printed in Crockford base32 so it cannot be misread. Your vault key is wrapped under it as well as under your master password. Forget the password, use the key, set a new one. Nothing is lost, and we still never held anything that could open your vault.
Using the Recovery Key issues a fresh one, because the old one has now been typed into a browser. You can also replace it at any time from Settings.
There is nothing anyone can do. We can clear the account so the address can be used again, but the data is unrecoverable by us, by you, by anyone. This is the price of the guarantee, and we would rather state it plainly than bury it.
What we can see
| Item contents | No. Encrypted in your browser under a key we do not have. |
|---|---|
| Item names, URLs, usernames | No. They are inside the same encrypted blob. |
| How many items you have | Yes. Row counts are visible. Sizes are padded, so lengths are not. |
| Your email address | Only by decrypting it with the Worker secret, which we do to send you mail. It is not readable from the database alone. |
| When you sign in, and from what kind of device | Yes. It is in your own activity log, encrypted at rest. |
| Your master password or Recovery Key | Never. Neither is transmitted, in any form, ever. |
Outbound requests
In normal use KalmPass makes no request to anywhere but its own origin. The single exception is the breach check, which you have to press a button to run: your browser SHA-1s a password locally and sends only the first five hex characters to Have I Been Pwned, which returns every hash sharing that prefix. The match is found on your device. We proxy the request so HIBP does not see your IP either.
What this does not protect against
Being straight about the limits is part of the job:
- A compromised device. Malware on your machine, or a browser extension with page access, can read your vault while it is unlocked. Nothing server-side can prevent that.
- A weak master password. A million PBKDF2 rounds multiplies an attacker's cost; it does not rescue a password that was guessable to begin with. Use a passphrase.
- A malicious server operator. We cannot decrypt what you have already sent, but in principle we could serve you altered JavaScript in future. That risk is inherent to every web-delivered password manager. It is why KalmPass ships no third-party code whatsoever: no CDN, no analytics, no framework, and no dependency that could be compromised to change what runs in your browser.
- Losing both keys. Covered above. Keep a backup.
Reporting a problem
If you find a vulnerability, please email security@kalmpass.net before disclosing it publicly. We will confirm receipt, keep you updated, and credit you if you would like that. We will not threaten anyone who reports a problem in good faith.
Last reviewed 15 September 2026.