Security
Most financial apps say “bank-level encryption” and show a lock icon. We’d rather show you our actual database.
What our accounts table actually looks like
This is a representative sample of how your account data is stored. Amber = AES-256-GCM encrypted. Blue = HMAC-SHA256 pseudonymized. Black = plaintext.
| user_id | name | institution | balance | type |
|---|---|---|---|---|
| a1b2c3d4e5f6...9f8e7d6c | enc:3a7f1b:c9e2d4:8b1f3a7e... | enc:7d4e2a:f1b3c5:2e9a4d7b... | $47,832.19 | Checking |
| a1b2c3d4e5f6...9f8e7d6c | enc:5c8d2e:a4f1b7:6d3e8c1a... | enc:9b2f4a:d7e3c1:4a8f2b5e... | $312,457.83 | 401k |
| a1b2c3d4e5f6...9f8e7d6c | enc:2f9a4d:b5c8e1:7a3f6d2b... | enc:4e1b7c:f8a2d6:5c9b3e7a... | $184,291.50 | Mortgage |
What our transactions table actually looks like
Transactions store amounts, descriptions, and categories in plaintext — they’re needed for real-time aggregation, tax calculations, and financial statements. But there is no name, email, or login credential anywhere in this table.
| user_id | description | amount | date | category |
|---|---|---|---|---|
| a1b2c3d4e5f6...9f8e7d6c | COSTCO WHSE #1234 | -$247.83 | 2026-02-24 | Groceries |
| a1b2c3d4e5f6...9f8e7d6c | EMPLOYER DIRECT DEP | $4,832.19 | 2026-02-15 | Income |
| a1b2c3d4e5f6...9f8e7d6c | VANGUARD BUY | -$2,000.00 | 2026-02-16 | Saving |
What the encryption achieves
Stored in plaintext
- • Financial amounts (balances, transaction amounts)
- • Transaction descriptions and dates
- • Account types (checking, 401k, mortgage)
- • Categories and rules
Encrypted or not stored at all
- • Your identity (no email, name, or login ID in the database)
- • Which bank any account belongs to (encrypted)
- • Account names and owner names (encrypted)
- • Bank login credentials (stored by Plaid, not us)
- • Any link between database rows and a real person
The critical insight: amounts without identity are anonymous. Our database contains financial data that cannot be tied to any person, bank, or account name without a secret stored separately from the database itself.
Three layers of protection
Identity pseudonymization
Your login identity is converted into an irreversible cryptographic hash (HMAC-SHA256) the moment you authenticate. This pseudonymized ID is the only user identifier in our financial data tables. No name, no raw user ID.
Stored as: a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef9f8e7d6c
Reversing this requires the HMAC secret, which is stored separately from the database. A database breach cannot link users to their financial data without this separate secret.
PII encryption
Every personally identifiable field — account names, institution names, owner names, and bank connection tokens — is encrypted with AES-256-GCM before it touches the database. Each value gets a unique random initialization vector and a tamper-proof authentication tag.
Stored as: enc:3a7f1b9c2e4d:c9e2d4f1b3a7:8b1f3a7e2c4d9b5f...
AES-256-GCM is the same standard used by governments and financial institutions worldwide. The “GCM” part means any tampering with the encrypted data is detected automatically.
Credential isolation
Your bank login credentials never enter our system. When you connect a bank, you authenticate directly with Plaid in their secure widget. ProCFO receives only an encrypted access token and read-only transaction data. We cannot log into your bank, move money, or see your password.
Technical details
| Identity hashing | HMAC-SHA256 with 256-bit secret |
| PII encryption | AES-256-GCM with 96-bit random IV per value |
| Authentication | Custom auth with bcrypt + JWT + TOTP MFA |
| Data in transit | TLS 1.3 (HTTPS everywhere) |
| Data at rest | Neon PostgreSQL encryption + application-level AES-256-GCM |
| Key storage | Encryption keys stored in Vercel environment variables, separate from database |
| Bank credentials | Managed by Plaid — never stored by ProCFO |
| API authorization | Every database query scoped by pseudonymized user ID — no cross-user data access |
| Tracking | No cookies, no third-party analytics, no ad trackers |
Why aren’t amounts and descriptions encrypted?
Honest answer: because the product wouldn’t work.
ProCFO generates financial statements, computes taxes across 51 jurisdictions, runs retirement projections, and categorizes transactions — all on the server. These operations require the database to aggregate, filter, and sort financial amounts. If amounts were encrypted, every query would require decrypting the entire database into memory first.
Instead, we encrypt the fields that link data to identity (names, institutions) and pseudonymize the field that links data to a login (user ID). The result:
- • Amounts without identity are anonymous. Someone spent $247.83 at Costco. Who? No way to tell from the database alone.
- • Encrypted names without amounts are useless. Someone named “enc:3a7f...” exists. So what?
- • Linking the two requires a secret stored nowhere in the database.
This is the same principle used by medical research databases and financial institutions: separate identity from data, protect the link between them.
Questions?
If you have security questions or want to report a vulnerability, contact us at admin@personalcfo.app. For privacy-related requests, see our Privacy Policy.