Home Account implements end-to-end encryption (E2E) to ensure your financial data remains private, even if the server is compromised. The server stores only encrypted blobs and cannot read your transactions, amounts, or category names.
Why E2E? Your financial data is yours, not ours. Even if someone hacks the server, they get encrypted blobs, not your transactions.
Derived from your password using Argon2id. Never leaves your browser or is sent to the server. This key decrypts your Account Keys.
2
Account Key (AK)
A random 256-bit AES key generated for each account. Encrypted with your User Key and stored in the account_keys table. Shared among all members of an account.
3
Data Encryption
All sensitive data (transactions, amounts, categories) is encrypted with the Account Key using AES-256-GCM before being sent to the server.
Why Argon2id? It’s the winner of the Password Hashing Competition and recommended by OWASP. It combines Argon2d’s resistance to GPU attacks with Argon2i’s resistance to side-channel attacks.
Not all data needs encryption. Here’s what we protect:
Field
Encrypted
Why
transactions.description
✅ Yes
Reveals spending habits
transactions.amount
✅ Yes
Your exact financial position
categories.name
✅ Yes
Personal category structure
transactions.date
❌ No
Needed for filtering by date
transactions.amount_sign
❌ No
+ or - (income/expense) for filtering
categories.color
❌ No
UI only, not sensitive
category_budgets.amount
❌ No
Budget limits, not actual spending
AI chats and investment profiles are NOT encrypted yet. Only transactions and categories are currently E2E encrypted. AI chat content is visible to the provider, so adding extra protection wouldn’t meaningfully increase privacy.
REFRESH (F5) | |-> Cookies persist (session valid) |-> Crypto store cleared (keys lost) | `-> Redirect to /unlock (PIN configured) OR /setup-pin (password = encryption source) | |-> Re-enter password or PIN |-> GET /auth/keys -> get key_salt, encrypted_keys |-> Re-derive UK, decrypt AKs `-> Back to dashboard
Key insight: The server never sees your password after login, never sees your decrypted keys, and never sees your plain text data. It’s just a dumb storage layer for encrypted blobs.
Conclusion: For a family accounting app, we’ll never hit limits that justify complex optimizations (Web Workers, virtualization, etc.). The trade-offs are accepted for this scope.
// Example: Calculate monthly totals client-sideconst calculateMonthlyTotal = (transactions: DecryptedTransaction[]) => { return transactions.reduce((sum, t) => sum + t.amount, 0)}// 5000 transactions take ~5ms on modern hardware// Imperceptible for personal finance use
CREATE TABLE transactions ( id VARCHAR(36) PRIMARY KEY, account_id VARCHAR(36) NOT NULL, date DATE NOT NULL, description_encrypted TEXT NOT NULL, -- IV + ciphertext + auth tag amount_encrypted TEXT NOT NULL, amount_sign ENUM('positive', 'negative', 'zero') NOT NULL, -- ... other fields);
If the server is hacked, attackers only get encrypted blobs. Without your password, the data is unreadable.
2
Database Leak
Database dumps contain only encrypted transactions and account keys. The User Key never leaves your browser.
3
Network Interception
All API calls use HTTPS. Even if TLS is compromised, the data is already encrypted with AES-256-GCM.
4
Admin Access
Even server administrators cannot read your data. They have no access to your User Key or decrypted Account Keys.
Password Loss = Data Loss: If you lose your password and don’t have a recovery phrase set up, your data is permanently unrecoverable. This is an inherent trade-off of E2E encryption.See the Password Recovery guide to set up recovery mechanisms.