A Technical Overview
Encryption that we can't break either.
A short paper describing the cryptographic architecture of MyLifePapers, what the protocol guarantees, and the limits we're explicit about.
Abstract
MyLifePapers is a local-first household-records vault. Records are encrypted on the device that owns them, with a key derived from the owner's passphrase via Argon2id. The encrypted blob is what travels: to other household members through their sync provider, or to our optional cloud. We never see the passphrase, the key, or the records. The data is mathematically inaccessible to us by design. This paper covers the protocol, the threat model we defend, the threats we don't, and the operational details that let an integrator verify our claims against the code.
Contents
- 01 Overview What the protocol guarantees, in a paragraph.
- 02 Architecture Three layers and the boundary between them.
- 03 What we never see Passphrase, key, records, telemetry.
- 04 Where your data lives Device, sync provider, our cloud.
- 05 Household sharing One key, many members, no shared passphrases.
- 06 Recovery Two paths back in. Neither involves us.
- 07 Threat model What we protect against, and what we honestly don't.
- 08 Technical reference Primitives, parameters, code locations.
- 09 Report an issue security@, 48 hour response window.
Overview
MyLifePapers is a local-first, end-to-end encrypted household record manager. Your passphrase derives a key on your device. That key wraps a data key, and the data key is what actually decrypts your records. The data key never leaves your device unencrypted.
Sync providers (Google Drive, Dropbox, OneDrive, iCloud, or our optional cloud) store opaque encrypted blobs. The encryption happens before anything crosses the network. If our servers are breached, the attacker sees ciphertext they cannot open. If your sync provider is breached, same. The key that opens these blobs exists in two places: your device's memory, and the recovery code we asked you to write down at setup.
Architecture
Three layers, each protecting what the layer below carries. The interesting line is the dashed one: that's where ciphertext crosses into systems we don't control.
┌─ on your device, never leaves ──────────────────────────┐ │ │ "my-passphrase" │ │ │ ↓ Argon2id (m=64MiB, t=3, p=1, salt=16B) │ │ [ KEK ] 32 bytes ─ key-encryption-key, in memory only │ │ │ ↓ XSalsa20-Poly1305 unwrap │ │ [ DK ] 32 bytes ─ data key, zeroed on lock │ │ │ ↓ XSalsa20-Poly1305 encrypt │ │ ┌─────────────────────────────────────┐ │ │ mlp-vault-v6 │ envelope │ │ ciphertext ‖ 24-byte nonce │ │ └─────────────────────────────────────┘ │ │ └─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ trust boundary │ ↓ HTTPS ┌─ untrusted: sync provider ──────────────────────────────┐ │ │ Google Drive / Dropbox / OneDrive / iCloud │ or MyLifePapers Cloud (Cloudflare R2) │ │ sees: opaque bytes · file size · mtime │ does not: passphrase · KEK · DK · records │ └──────────────────────────────────────────────────────────┘
What we never see
We have built the system so that the following are inaccessible to us, not by policy but by construction. A subpoena could not compel us to produce them; we don't have them.
- Your passphrase
- Used on your device to derive a key, then dropped from memory. Never transmitted, never persisted in plaintext, never reaches our servers in any form.
- The data key
- Held only in memory while the app is unlocked. Wrapped on disk with your passphrase. Zeroed from memory when the app locks (idle timeout, system suspend, screen lock).
- Your records
- Encrypted on your device before they leave. We receive opaque bytes: ciphertext indistinguishable from random.
- What you store, how you use it, when you open it
- No analytics SDKs. No telemetry pings. No feature-usage tracking. We see what's necessary to validate your license and route encrypted sync traffic, nothing more.
Where your data lives
The working copy is on your device. Encrypted backups exist in two other places. You control both.
- i.
On your device, decryptable.
A local SQLite database, protected by your operating system's file encryption: FileVault on macOS, BitLocker on Windows, Data Protection on iOS, file-based encryption on Android. Accessible only when the device is unlocked. The working set you actually use day to day.
- ii.
On your sync provider, encrypted.
One encrypted blob per household member, written to a folder you choose in your cloud storage. We never have access to your Google, Dropbox, Microsoft, or iCloud account. You can revoke our sync app's access at any time from your provider's settings without telling us.
- iii.
On our cloud, optional, also encrypted.
For cross-account family setups (where members use different Google accounts, or who prefer not to use a third-party cloud), we offer paid sync via our Cloudflare R2 bucket. Same encryption applies: we hold ciphertext we cannot read. Authenticated by your license key alone; no email, no password, no account on our side.
Recovery
There are two paths back into your vault. Neither involves us.
Path A · the recovery code
At setup we generate a 24-character recovery code, formatted as six groups of four. You see it once. With it, you can unlock your vault on a new device or reset your passphrase. The recovery code uses the same Argon2id derivation as your passphrase. It is a second equal key, not a backdoor. Write it down. Store it the way you'd store the title to a house.
Path B · without either
Your data is mathematically inaccessible. We cannot help. We have no backdoor, no master key, no reset option. The property that protects your data from us also protects it from password resets. This is the trade-off of true end-to-end encryption. It is a feature, not a missing one.
The remedy is upstream of failure: save the recovery code when prompted, and back it up the way you'd back up irreplaceable physical documents: paper in a safe deposit box, sealed with the rest of your estate planning, given to a trusted attorney.
Threat model
Every encryption product makes choices about what it protects against. The honest version is to list both sides. Here is ours.
What we defend
- 7.1
Server breach. If Google, Dropbox, Microsoft, iCloud, or our own R2 bucket is compromised, attackers see encrypted blobs they cannot decrypt without your passphrase or recovery code.
- 7.2
Network attackers. All traffic is TLS in transit. Sync requests authenticate via license key in a custom request header, not a cookie, which closes off CSRF by construction.
- 7.3
Lost or stolen device. Auto-lock triggers after five minutes of inactivity, on system suspend, and on OS screen lock. The data key is zeroed from memory at each event. The vault is unreadable while the device is locked.
- 7.4
Other apps on your phone. The mobile app uses the iOS Keychain or Android Keystore with access groups scoped to our signing identity. Other installed apps cannot read our keys, even with system-level access in their own sandbox.
- 7.5
Compromised sync metadata. The folder structure on your sync provider exposes file sizes and modification times, not content, names, or types. Record filenames are opaque hashes.
What we honestly do not defend
- 7.6
Malware on a device you are actively using. If your laptop is keylogged, no encryption product can help: we are decrypting your records for you in memory by design. This is the irreducible cost of being usable.
- 7.7
A weak passphrase combined with a leaked vault blob. Argon2id makes brute force expensive (roughly half a second per attempt on modern hardware), but a six-character common password is still attackable. Use a passphrase, not a password. Three random words beats one clever word.
- 7.8
A household member you've shared with going rogue. Once you share a record with someone, they have a decryption-capable copy on their device. Removing them from the household stops new shares from reaching them, but you cannot reach into their device after the fact.
- 7.9
Targeted nation-state attacks. If a nation-state targets you specifically with on-device exploits, our threat model does not cover you. Use a hardware-backed key store, an air-gapped device, and trained opsec, and probably not us.
Technical reference
For the security-curious and for OAuth or integration reviewers.
Every value here is traceable to the code under
apps/desktop/src/main/ and
apps/mobile/src/lib/; this section reads as a spec,
not as marketing.
- Encryption primitive
-
libsodium
crypto_secretbox_easy(XSalsa20 + Poly1305). Authenticated encryption: ciphertext carries an integrity tag; verification fails on tamper. - Key derivation
-
Argon2id. Memory
64 MiB, time3, parallelism1, 16-byte random salt. OWASP's recommended profile for sensitive applications. - Data key (DK)
-
256-bit symmetric key from libsodium
crypto_secretbox_keygen. Generated locally per household. Wrapped per-member with each member's passphrase-derived KEK. - Member sharing
-
crypto_box_seal(X25519 + XSalsa20-Poly1305 + BLAKE2b) to a recipient member's public key. New members never need the admin's passphrase, only their own. - Vault envelope
-
Versioned wire format, currently
mlp-vault-v6. Backwards-compatible by design: older versions remain readable indefinitely; version bumps pair with read paths that handle both. - At-rest on iOS
-
Keychain with
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, app-group restricted to our signing team ID. SQLite database file pinned toFileProtectionType.complete. - At-rest on Android
-
Keystore-backed key material.
allowBackup="false"with explicitdata_extraction_rulesexcluding every domain from cloud backup and device transfer. - Auto-lock
- Five-minute idle timeout, lock on system suspend, lock on OS screen lock. The data key is zeroed from memory on each event.
- Trusted Access & Send
- Per-share encryption with PBKDF2-SHA256 → AES-256-GCM via WebCrypto. The access code lives in the link and never reaches our servers; the blob is unreadable to anyone who intercepts the link without the code.
- Random sources
-
randombytes_bufin libsodium paths,crypto.getRandomValuesin the renderer,crypto.randomBytesin the Node main process.Math.randomis not used for any key, nonce, or token in this codebase. - On-device AI
- Document classification and embedding for search run entirely on your device. Records are never sent to a third-party model. Optional summarization runs a small on-device model, Gemma 4, falling back to the platform's native model where available: Apple Foundation Models (iOS 26+), Gemini Nano (Android), or Phi Silica (Windows). All on-device.
Report an issue
We take security reports seriously. Email security@mylifepapers.com with reproduction steps. We aim to respond within 48 hours and coordinate disclosure responsibly.
We do not run a formal bug bounty. Researchers who report verified security issues receive public attribution if they want it, and our genuine thanks.
§ MyLifePapers Engineering · revised 25 June 2026 · v6