1. Definition
Encryptio is zero-knowledge at rest: Postgres stores only vault, note, and key-backup ciphertext. The master key is never written to the database.
This is not client-only end-to-end encryption for vault items. At login the master key is derived from the password and held on the server for the session, so the web app and the extension API can decrypt data.
During an authenticated session the server can read vault plaintext. Without the password, recovery key, or an active session, a database copy alone is not enough to decrypt the vault.
2. Threat model
The useful question is not “can Encryptio ever decrypt?”, but “in which state does the server hold the master key?”.
| Status | Can the server read vault plaintext? |
|---|---|
| No login; ciphertext in Postgres | No: the master key is not in the database |
| User signed in (web or extension) | Yes: master key derived at login and held for the session |
| Password reset without recovery key | No: the new master key does not open the previous ciphertext |
| Digital Legacy inactivity grant | Yes, delegated, only if the owner enabled escrow |
3. Where encryption happens
The encryption point depends on the data type. Shared files are encrypted in the browser before upload. Vault items are not: the application encrypts them when writing to the database.
| Data | Where it is encrypted | What remains on the server |
|---|---|---|
| Vault passwords and notes | Application server, on database write | AES-GCM ciphertext only |
| Shared files | Browser, before upload | Encrypted blob on disk only |
| Internal sharing between Encryptio users | Server: per-share AES key wrapped with the recipient RSA key | Ciphertext and wrapped key |
| External sharing (link) | Server, with a key derived from the link token | Ciphertext and token hash |
| Extension local cache | Extension service worker | None: it stays on the device |
The authenticated extension API returns vault items already decrypted. It is a secret channel between the session (or Bearer token) and the client, not a ciphertext dump.
4. Key generation
Login password and master key
At registration each account receives a 16-byte per-user salt. The password has two distinct uses:
- Login verification: Argon2id hash (with an application pepper) stored as password_hash.
- Master-key derivation: PBKDF2-HMAC-SHA256, 600,000 iterations, 256-bit output, from password + pepper + salt. This key is not persisted in Postgres.
After a successful login the master key stays in Redis, keyed by user and session, with a TTL (12 hours by default). It is never written to the Flask session cookie. Logout deletes the Redis key. Within a single request it is held only in the Flask g context.
Recovery key
At registration, and again on every authenticated password change, the server generates 32 random bytes (256 bits). The recovery key is shown once, in Base64. It encrypts a backup copy of the master key. The plaintext recovery key is not stored.
Other keys
- Email: an AES key derived deterministically from the user identifier and a system secret, so login and reset remain possible without the master key.
- Internal sharing: RSA-2048 key pair per user; the private key is encrypted with the recipient’s master key.
- External sharing: AES key derived from the link token (the plaintext token is not in the database).
- File share: AES key derived in the browser from the share password chosen by the user.
5. Algorithms besides AES-256
AES-256-GCM is the AEAD for the vault, notes, TOTP secrets, the master-key backup, and sharing payloads. Around it we use distinct primitives for authentication, key derivation, wrapping, and lookup.
| Use | Algorithm | Parameters |
|---|---|---|
| Login password hash | Argon2id | time cost 3, memory 64 MiB, parallelism 4 |
| Master key | PBKDF2-HMAC-SHA256 | 600,000 iterations, 32 bytes, per-user salt and pepper |
| Vault, notes, blobs, key backup | AES-256-GCM | 96-bit nonce prepended to the ciphertext |
| Email lookup | HMAC-SHA256 | normalized address; separate email ciphertext |
| API, legacy, and external-share tokens | SHA-256 | hash only in the database, with a pepper |
| Internal sharing | RSA-2048-OAEP | SHA-256; wraps a per-share AES key |
| File share (browser) | PBKDF2-HMAC-SHA256 + AES-256-GCM | 100,000 iterations in the client, then ciphertext upload |
| Two-factor authentication | TOTP | secret encrypted at rest with the master key |
| Blockchain timestamp | SHA-256 | only the plaintext file digest, computed in the browser |
6. Account recovery
Login-password reset and vault recovery are distinct operations. An email link proves you control the address, not that you hold the master key.
Reset with recovery key
- The user confirms the reset link and provides the recovery key.
- The server decrypts the master-key backup with that recovery key.
- With the previous master key it decrypts the vault, notes, and TOTP secret.
- From the new password it derives a new master key and re-encrypts everything.
- The backup is re-wrapped with the same recovery key. Email is not re-encrypted: it uses a key independent of the password.
Reset without recovery key
You can set a new password and sign in, but the new master key does not open the existing ciphertext. Vault data stays unreadable until you use the original recovery key to recover it, or it is lost.
Password change from an authenticated session
If you still know the current password, the server derives the master key, re-encrypts the data with the new master key, and issues a new recovery key to save in place of the previous one.
Other mechanisms, not equivalent
- 2FA recovery codes unlock only the second factor, not the vault.
- Digital Legacy can grant read-only access to an email contact after a waiting period. Master-key escrow for inactivity is explicit and opt-in.
Losing both the password and the recovery key means permanent loss of vault data. That is intentional: without one of those two proofs, nobody — including Encryptio — can reconstruct the master key from the database alone.
7. Related features
- External shares: always time-limited, revocable by the sender; the email contains only the link, not the content.
- Manifest V3 extension: local cryptography and API calls go through the service worker, not content scripts.
- Chia timestamp: proof of existence of the hash, not an eIDAS timestamp and not a notary act.
8. What this model is not
We do not claim that “even Encryptio can never decrypt”. That sentence would describe a vault encrypted only in the browser, with a key that never leaves the device. Encryptio encrypts the vault at rest and decrypts in session to provide the web app, API, and account recovery.
The security value is this: an attacker with only the database (or a cold backup) does not get vault passwords. An attacker who compromises an application process during an active session is in the same perimeter as that session’s master key: that is why the key lives in Redis with a TTL, not in the cookie, and logout deletes it.
The Security page remains the summary for everyone. This whitepaper is the technical description of the model.
Want to use Encryptio with this key model?