Skip to content

What is encrypted

Two keys exist in Badlen and they do unrelated jobs. One seals two columns in the database; the other seals a file you downloaded. Neither can stand in for the other.

SECRET_ENCRYPTION_KEY lives in your .env and nowhere else. It is 32 bytes, base64, and the instance refuses to start without it. Everything it encrypts is encrypted with AES-256-GCM, at rest, in the database.

It seals exactly two things:

  • Your two-factor secret, written when you enrol an authenticator app and opened again on every code check.
  • Your provider API keys, today the Moralis key, used to find the tokens held at a wallet address.

That list is the whole of it, and it is also the answer to what a leaked key would expose.

Everything else is stored in the clear: your accounts, balances, operations, holdings, lots, prices, daily net worth points, categories, rules, preferences and achievements. So are your wallet addresses, which ride on the account they belong to: an address is public by construction and the wallet sync reads it on every run. Your password is neither in the clear nor encrypted: it is hashed with argon2id, which is one way and has no key.

The passphrase you type when exporting seals the downloaded file itself, and is used for nothing else. The key is derived from it with argon2id, over 64 MiB of memory, three passes and a fresh salt per file. The salt and those parameters travel inside the file, so it opens on any Badlen without you writing anything down.

It never touches SECRET_ENCRYPTION_KEY. That is the point: a backup whose readability depended on a key in a .env would stop opening the day that key was regenerated. A sealed export opens on a machine that never saw the original instance, and the secrets it carries are re-encrypted under the key of the instance doing the restoring.

Export your data is where you choose between a sealed file and a plain one, and what each carries.

A pg_dump carries the ciphertext of those two columns and none of the key, so a restore into an instance holding a different one gives every figure back and neither sealed thing. Restoring under another key is the whole of that case and the way out of it. It is why SECRET_ENCRYPTION_KEY belongs beside the dump, in a different place, and why a passphrase-sealed JSON export has none of the problem.

Privacy