How our encryption works
Turn encryption on and your mood entries and health data become unreadable to us. Explained below twice — once in plain English, once in full technical detail.
Part one
In plain English
Think of it as a locked box
When you write an entry, your phone puts it in a locked box before sending it to us. We receive the box. We store the box. We never receive a key, so we cannot open it — and neither can anyone who steals a copy of our database, because all they get is boxes.
This is different from most apps, which lock the door of the warehouse but keep a key to every box inside it.
Where your key comes from
The key is made from your password, on your phone. It is never sent to us, not even briefly, and not in a scrambled form we could unscramble later. Your password itself still reaches us when you log in — but the key made from it does not.
Three keys, one box — so you don't get locked out
Real encryption has a real risk: lose the key and the data is gone forever. So your box has three different keys that all open it.
- Your password — how you normally get in.
- Your phone itself — it quietly holds a key, which is why the app doesn't nag you for a password every time.
- Your recovery code — a spare key we show you once, when you turn encryption on. Keep it somewhere safe. It is how you get back in if you ever reset your password.
Because all three open the same box, resetting your password only replaces one key. Your entries are never re-locked or lost in the process.
The honest catch
If you forget your password and lose your recovery code, your entries are gone. Not "gone unless you email support" — actually gone. We cannot recover them, because we never had a key.
That is the trade. An app that can recover your data on request is an app that can read it. You cannot have both.
Part two
The technical detail
Everything below is implemented with the Web Crypto API on-device. Figures are the actual parameters in use, not recommended defaults.
Content encryption
- AES-256-GCM, with a fresh 12-byte random IV per operation and the authentication tag retained, so tampering is detected rather than silently decrypted.
- Mood entry payloads are padded to 4 KB blocks before encryption, so ciphertext length does not reveal whether you wrote a word or a page.
- The encrypted blob holds the mood score, note, emotions, physical sensations, triggers, activities and on-device insights. Only the entry ID and timestamp remain in the clear, because sync cannot order or reconcile entries without them.
Key derivation
- Argon2id — 64 MiB memory, 3 iterations, parallelism 4, 32-byte output, with a random 16-byte per-slot salt. Memory-hardness is the point: it makes large-scale GPU guessing expensive rather than merely slow.
- PBKDF2-HMAC-SHA256, 600,000 iterations as a fallback where WASM is unavailable. This is weaker — it is not memory-hard — and our test suite fails if the fallback is taken on a platform that supports Argon2id.
- HKDF-SHA256 for domain separation, so the wrapping key cannot be confused with any other key derived from the same material.
Envelope and key slots
A random AES-256-GCM data key (DEK) encrypts your content. That key is never used directly by a password. Instead it is wrapped withAES-KW (RFC 3394) under a key-encryption key, once per slot:
- Password slot — KEK from Argon2id over email + password.
- Device slot — a non-extractable AES-KW key generated in the browser and held in IndexedDB. JavaScript cannot export its raw bytes, so a copied database file yields an opaque handle rather than a key. It is software-backed, not hardware-backed; a compromise of the OS or of code running in our origin defeats it.
- Recovery slot — KEK from Argon2id over a 160-bit recovery code, rendered in Crockford base32 (no I, L, O or U) so transcription errors resolve rather than fail.
All three wrap the same DEK, which is why a password change re-wraps one slot in milliseconds instead of re-encrypting your history, and why a password reset does not orphan your data. The server refuses to delete your last remaining slot.
Server-side enforcement
The server does not merely accept ciphertext — it actively refuses plaintext alongside it. If a client sends both, the readable copy is discarded rather than stored, and a database CHECK constraint rejects any row holding both forms. That constraint sits in the database rather than the application so it holds regardless of which code path does the writing.
What this does not protect
No scheme protects everything, and a page listing only strengths is not worth trusting. Here is what ours does not cover.
- It is optional and off by default. Until you turn it on, synced entries are readable to us.
- Metadata is visible. We can see when you wrote an entry and how often — just not what it says.
- Health metric types are visible. We can see that you record sleep or blood glucose, because sync deduplication depends on those labels. The readings themselves are encrypted.
- Coverage is not yet complete. Encryption applies to mood entries and health data. Streaks, garden progress and goal settings are not yet encrypted.
- No per-entry forward secrecy. Each entry is encrypted under the same data key, so an attacker who obtained that key could read everything encrypted with it.
- Your device is the weak point. Anyone who unlocks your phone sees what you see. Use a device passcode and the app's own lock.
- Losing both password and recovery code is final. We cannot help, because we cannot read your data.
Location doesn't need encrypting, because we never get it
Encryption protects data we hold. Location is handled by not holding it at all, which is a stronger guarantee than encrypting it would be.
- The iOS and Android apps cannot read your device location. They do not request the location permission, so there is nothing to grant. You pick a city yourself if you want weather features.
- It stays on your device. Your chosen location is excluded from account sync, and our servers strip it if an older version of the app sends it.
- It exists for two features: comparing your mood against local weather and daylight, and showing the right country's crisis helpline numbers.
- Weather lookups go direct from your phone to the weather and place-name providers, not through us. We never see those requests. Each provider handles them under its own privacy policy.