How pswrds protects your passwords
Security design · last updated 6 October 2026
This page describes exactly how pswrds encrypts and stores your data, what it protects against and what it doesn't. It's written for technical readers, security researchers and auditors. The full source code is on GitHub under the AGPL-3.0 licence. If you only want the short version, read the questions and answers.
01Design goals
- Zero knowledge. Passphrases and decryption keys never leave your device. The server stores only encrypted data it cannot read.
- Nothing to learn from the database. Every vault looks the same on the server: the same number of slots, all the same size, whether it holds one wallet or several.
- Plausible deniability under coercion. A passphrase handed over under pressure opens an ordinary-looking wallet, and nothing shows whether other wallets exist (see duress passphrases).
- No accounts, no tracking. No email address, no cookies, no analytics, no third-party resources.
02What the server stores
| Item | What it is |
|---|---|
| Vault ID | SHA-256 of "pswrds-account:" + username. The username is lowercased and Unicode-normalized (NFC) first, and is never sent. |
| Salt and iteration count | 16 random bytes per vault, and the PBKDF2 iteration count (600,000). |
| 8 slots | Each exactly 65,564 bytes of encrypted data. Unused slots are random bytes of the same size. |
| Slot versions | A random number per slot that changes on every save, so two devices can't silently overwrite each other's changes. |
| Write-token hash | SHA-256 of the vault's write token (see section 05). |
| Creation time, paid flag | When the vault was created, and whether it has unlimited records. |
The server never receives a passphrase, a key, a record, a wallet name or the number of wallets in a vault.
03Keys and encryption
- Each passphrase is turned into a 256-bit key with PBKDF2-HMAC-SHA256, 600,000 iterations, using the vault's salt. Every passphrase of a vault has its own key.
- A slot holds a JSON document padded with random bytes to exactly 65,536 bytes: a 4-byte length, the JSON, then padding. Because every slot has the same size, its size reveals nothing about how much it holds.
- The padded document is encrypted with AES-256-GCM using a fresh random 96-bit nonce on every save. The additional authenticated data is
"pswrds:" + vault ID + ":" + slot number, so a slot can't be moved to another position or another vault. - To unlock, the app derives the key and tries it on all 8 slots. It always tries all of them, and it runs the key derivation even when the username doesn't exist, so timing doesn't reveal which slot opened or whether a vault exists.
All cryptography uses the browser's built-in Web Crypto API. Random numbers come from crypto.getRandomValues. Generated passwords are 20 characters from a 70-character set (about 122 bits).
04Several passphrases in one vault
A vault created without an invitation code has one wallet in one slot; the other seven are random data. A vault created with an invitation code has a main slot whose document lists the other slots in use: for each one, what it does, its label and its key. That's how the main passphrase can open and manage everything, while each other passphrase opens only its own slot.
- Wallet passphrase: its slot holds that wallet's records and nothing about the others.
- Delete passphrase: its slot holds which slots to destroy and, optionally, the key of the wallet to show afterwards. When it's typed, the app overwrites those slots with fresh random bytes and replaces its own slot with a pointer to the wallet it showed (or an empty wallet). From then on the same passphrase simply opens that wallet.
- The next time the main passphrase is used, the app compares its list with the slots and reports what happened.
Nothing on the server distinguishes these kinds: every slot is the same size and every unused slot is indistinguishable from an encrypted one.
05Who can change a vault
When a vault is created, the app makes a random 256-bit write token and stores it inside every slot it creates. The server keeps only its SHA-256 hash and accepts a change only with the token, so only someone who can open a slot can change the vault. Changing the main passphrase issues a new token. Each save names the slot version it replaces; if another device saved in the meantime, the server refuses (HTTP 409) and the app reloads instead of overwriting.
06Fingerprint and face unlock
Optional, for one wallet per device and never for the main passphrase. The app creates a passkey and uses the WebAuthn PRF extension: after a successful fingerprint or face check, the device's authenticator returns a 32-byte secret that never leaves the device. HKDF-SHA256 turns it into an AES-256-GCM key, which encrypts that wallet's key and the username in the browser's local storage. The server isn't involved. Changing that wallet's passphrase makes the stored copy useless, and the app removes it.
07Payments and invitations
- Payments go through NOWPayments. The app asks the server for an invoice without sending any vault ID. A random claim secret, kept inside the encrypted wallet, lets that wallet collect a license code once the invoice is paid. Redeeming the code marks the vault as paid. The server never records which payment belongs to which vault.
- Invitation codes are stored only as hashes. A used code is recorded as used, without the vault it was used for, so the database can't tell a vault created with a code from one created without. Codes on request need a small proof-of-work puzzle and are limited per connection and per day.
08The web app itself
- HTTPS only, with HSTS. A strict Content Security Policy allows resources only from pswrds.com: no third-party scripts, fonts or images, and the app can only talk to its own server.
- No cookies. IP addresses are used only as keyed hashes for rate limiting (300 requests per 10 minutes per connection).
- The vault locks after 10 minutes without activity and clears everything decrypted from the page.
- An on-screen keyboard with shuffled keys lets you enter a passphrase on a computer you don't fully trust without typing it.
- Backups are a file with the vault's encrypted slots and salt: as safe, and as dependent on your passphrase, as the vault itself.
09What pswrds protects against
- A breach of our server or database, or a demand for our data. Only encrypted slots can be handed over. Opening one requires guessing a passphrase, which is slow by design.
- Us. We can't read your records, reset your passphrase or tell how many wallets you have.
- Someone on the network. Everything travels over HTTPS, and is encrypted before it's sent.
- Being forced to unlock (for vaults with an invitation code). You can give a passphrase that opens a believable wallet, or one that silently destroys chosen wallets.
10What it doesn't protect against
- Weak passphrases. Anyone who knows your username can download your encrypted vault and try passphrases offline, so a weak passphrase can be guessed. Use several random words.
- A compromised device. Malware on your phone or computer can see what you see once you've unlocked.
- A compromised website. Like every web app, pswrds loads its code from our server each time. Someone who took over the server could serve altered code. The source code is public on GitHub, so anyone can compare it with the code the site serves.
- Old copies. A delete passphrase or a passphrase change can't reach copies made earlier: an earlier copy of the database, or the hosting company's backups (kept for several weeks).
- Comparing copies over time. Someone with copies of the database from different times can see which slots changed between them, which hints at how many slots are in use.
11Known limitations and planned work
- One write token per vault. Anyone who can open any slot (including a wallet handed over under pressure) could, with technical knowledge, overwrite or delete the whole vault, though not read it. Separate write tokens per slot are planned.
- Usernames can be checked. The server reveals whether a username exists. Hiding that is planned.
- Key derivation. PBKDF2 is not memory-hard. Argon2id is planned for new vaults.
- Inline scripts. The Content Security Policy still allows inline scripts. Moving the code into separate files, so this can be removed, is planned.
- Clipboard. Copied passwords stay on the clipboard until you clear them.
- No independent audit yet. We intend to have one and will publish the report here.
12Security changes
- October 2026, internal review:
- Records coming from a wallet are now checked before they are displayed. Previously someone with a shared wallet's passphrase could have planted content that ran code when the main passphrase viewed that wallet.
- Slot versions are now random numbers instead of counters. Counters showed how often each slot had been saved, and so which slots were in use.
- Key-derivation settings from the server or a backup file are checked before use.
13Reporting a vulnerability
Please write to info@pswrds.com with a description and, if possible, steps to reproduce. We'll reply within a few days, fix confirmed issues as quickly as we can, and credit you here if you'd like. Please don't access other people's vaults or disrupt the service while testing; use the demo or your own test vaults. Our security.txt has the same details.