Encryption and zero-knowledge architecture
The technical version. Published 21 September 2026. About 8 minutes.
This is the detailed companion to the security overview, written for people who want parameters rather than adjectives. Everything below is one implementation in a shared core used by the web, iPhone and Android apps, built on @noble/hashes and @noble/ciphers — audited, pure-JavaScript implementations of standard primitives. There is no custom cryptography anywhere in KeyNesta, which is the single most useful thing a small team can say about its own crypto.
Key hierarchy
passphrase ──scrypt(N=2^15, r=8, p=1)──▶ KEK ──AES-256-GCM──▶ wraps MK ("keyslot")
MK (random 256-bit master key)
├─ AES-256-GCM ▶ local vault envelope AAD "keynesta.vault.v1"
└─ AES-256-GCM ▶ each sync object AAD "keynesta.obj.<type>.<id>"
backup password ──scrypt──▶ backup key ──AES-256-GCM──▶ backup file
AAD "keynesta.backup.v1"
Three properties fall out of that shape:
- The master key is random, not derived. Your passphrase never encrypts your data directly; it only unwraps the key that does. Changing the passphrase rewrites one keyslot — a few hundred bytes — and the vault itself is never re-encrypted, never decrypted to disk, and never exposed during the change.
- scrypt is memory-hard. N=2^15, r=8, p=1 costs roughly 32 MB of memory per guess, which is what makes large-scale GPU brute force expensive rather than merely tedious. Input is NFKC-normalized so the same passphrase typed on two platforms derives the same key, and each keyslot carries its own 16-byte random salt.
- There is no oracle. A wrong passphrase produces a GCM authentication failure and nothing else: no hint, no partial plaintext, no distinguishable error. Verification and decryption are the same operation.
What the AAD is for
Every ciphertext is bound to its role with additional authenticated data: the vault envelope to its format version, each synced object to its type and id, each backup file to the backup format. AES-GCM authenticates the AAD without encrypting it, so a ciphertext cannot be moved from one slot to another. An attacker with write access to the sync store cannot take the ciphertext of one entry and serve it as a different entry, or replay a backup blob into the vault envelope: the tag stops verifying and decryption fails.
What the server actually stores
The sync backend is a Go service on MongoDB. Per user it holds exactly this:
| Stored | Not stored |
|---|---|
| Email and a bcrypt password hash, for the sync account only | Your vault passphrase, or anything derived from it |
| The keyslot — which is already ciphertext | The master key, in any form the server could use |
| Per object: id, type, version, nonce, ciphertext, deleted flag | Secrets, generated codes, issuer or account names, tags, notes |
| Device records and a last-seen timestamp | Your space and group names, or the shape of your hierarchy |
Objects are encrypted individually rather than as one blob, which is what makes incremental sync possible without leaking structure: the server can tell that you have some number of objects and that one of them changed, and that is the extent of it. The test suite asserts on the actual wire payload that a known secret does not appear in it — a regression test, not a promise.
Sync protocol
- Version-checked upserts. Every write carries the version the client last saw. A stale write is rejected and the stored object comes back as a conflict, which the client merges locally. Two edits to the same entry both survive; the losing one becomes a "(conflict)" copy.
- Tombstones are final. Deletions replicate as tombstones and the server refuses resurrection, so a device that was offline during a delete cannot push the entry back.
- Revocation is checked per request. Revoking a device invalidates its token at the authentication check on the next call, not at its next login.
- New device: log in, fetch the keyslot, unwrap it locally with the passphrase, then pull objects. The passphrase is never transmitted, and the sync account password never decrypts anything — the two are deliberately different credentials with different jobs.
Multiple vaults
Each vault on a device is fully independent: its own random master key, its own scrypt keyslot with its own salt, its own envelope, its own optional sync account. Compromising one vault's passphrase reveals nothing about any other vault on the same device. The registry of vault names and ids is stored in plaintext by design — the names are labels on the lock screen, not secrets, and pretending otherwise would be theatre.
At rest, per platform
| Platform | Envelope | Master key while unlocked |
|---|---|---|
| Web | Browser local storage, ciphertext only | Memory only; discarded on lock and auto-lock |
| iOS | App storage, ciphertext only | Memory; optionally in the Keychain behind Face ID, device-only, when-unlocked |
| Android | App storage, ciphertext only | Memory; optionally in Keystore-backed storage behind biometrics |
Biometric unlock stores the master key, never the passphrase, and turning biometrics off removes it. The passphrase path always works, because a fingerprint sensor is a convenience and should never be the only door.
Threat model: what this does and does not stop
Handled. A breach of the sync servers yields ciphertext and wrapped keys. A stolen but locked device yields an encrypted envelope. A network attacker with TLS stripped still sees only encrypted objects. A malicious or compelled operator — us — cannot read vaults, because we hold nothing that opens them.
Not handled, and worth being clear about.
- An offline attack on a weak passphrase. An attacker holding your keyslot can try passphrases against it forever. scrypt makes each attempt expensive; it does not make a six-character passphrase safe. Length is the only defence that scales.
- A compromised device. Malware or a malicious browser extension on an unlocked session can read what you can read. Auto-lock narrows the window and does not close it.
- Clipboard history. Clearing the clipboard after a copy is best-effort on every platform; operating systems keep histories we cannot reach.
- Browser storage is not hardware-backed. The web vault is origin-bound and encrypted, but there is no secure element involved. The mobile apps are the stronger option where the hardware exists.
- We ship the client. Zero-knowledge protects you from the server; it does not protect you from a malicious update to the app that computes the codes. That is true of every authenticator, including the ones that do not mention it.
- No independent audit yet. The design is standard and the primitives are audited libraries, but KeyNesta itself has not had a third-party cryptographic review. Until it does, read "secure" as "well-designed and tested", not "audited". We will publish the result when there is one.
Vulnerability reports go to security@keynesta.com and are welcome.
Frequently asked questions
Why is the master key random instead of derived from my passphrase?
So the passphrase can change without re-encrypting anything. A derived key means every passphrase change rewrites the whole vault, which is slow, risky and involves holding the plaintext. Wrapping a random master key means a passphrase change rewrites one small keyslot and the data never moves.
Which cryptographic libraries does KeyNesta use?
The audited @noble/hashes and @noble/ciphers packages, for AES-256-GCM, scrypt and HMAC. They are the same implementation on web, iPhone and Android, so a vault created on one platform opens identically on another. KeyNesta implements no primitives of its own.
Could a KeyNesta update be made to steal my vault?
Yes, in principle, and so could an update to any authenticator app that runs code on your device. Zero-knowledge sync removes the server from your trust boundary; it cannot remove the client. What limits the risk is that exports are standard and portable, so leaving costs you nothing but a few minutes.
Is the sync account password the same as my vault passphrase?
No, and it must not be. The account password authenticates you to the sync service and is stored there as a bcrypt hash. The vault passphrase unwraps your master key and never leaves your devices. Someone who steals the account password gets ciphertext.
Try KeyNesta free. The web app needs no sign-up.
Open the web app All platforms