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:

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:

StoredNot stored
Email and a bcrypt password hash, for the sync account onlyYour vault passphrase, or anything derived from it
The keyslot — which is already ciphertextThe master key, in any form the server could use
Per object: id, type, version, nonce, ciphertext, deleted flagSecrets, generated codes, issuer or account names, tags, notes
Device records and a last-seen timestampYour 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

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

PlatformEnvelopeMaster key while unlocked
WebBrowser local storage, ciphertext onlyMemory only; discarded on lock and auto-lock
iOSApp storage, ciphertext onlyMemory; optionally in the Keychain behind Face ID, device-only, when-unlocked
AndroidApp storage, ciphertext onlyMemory; 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.

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