Encryption at rest for sensitive ledger fields
Encrypt at the column level, not just the disk. Your DBA shouldn't see PII.
By Solomon Ajayi · Free to read, no signup
Disk-level encryption (LUKS, AWS EBS encryption) protects against physical theft of the storage medium. It does NOTHING against a DBA who can log into the database, run SELECT, and read every user's email, phone number, and account number in clear text. For sensitive fields, PII, account numbers, card metadata, transaction descriptions that might contain identifying info, you need APPLICATION-LEVEL encryption with keys managed in a KMS the DBA doesn't have access to. The journal entries themselves can stay in clear (amounts and account codes aren't PII), but metadata fields with identifying info get encrypted at write and decrypted on read by the application. This lesson posts an entry with encrypted-at-rest metadata.
Disk encryption answers the wrong threat. It protects against someone stealing the physical drive, but it does nothing against the person who is supposed to have access: a DBA who logs in, runs SELECT, and reads every user's name and account number in plaintext. The data is decrypted the moment the database serves a query, so the disk being encrypted is irrelevant to anyone holding a database credential. The threat that matters for PII is a legitimate login, not a stolen disk.
The defense is to encrypt sensitive fields in the application, above the database, with keys the DBA cannot reach. The amounts and account codes stay in clear because they are not PII and the ledger needs to sum them, but a description like a full name plus a bank account number gets encrypted before INSERT. It lands in the column as ciphertext wrapped in an envelope such as enc, a key version, and base64. The read path recognises that prefix and calls the key service to decrypt only when it actually needs the plaintext.
Putting the keys in a separate KMS is what makes this least privilege rather than theater. The DBA keeps read access to the database, which they need to do their job, but has no permission in the KMS, so to them the protected fields are useless ciphertext. The same split makes a leaked database snapshot harmless, since it carries no keys, and makes key rotation mechanical: re-encrypt under a new key version and retire the old one, with the version stamped right in the envelope.
Worked example, step by step
Deposit ₦10,000 with PII in metadata: encrypted at write
Standard deposit. The sourceEventId references a payment processor transaction ID, clear text, not sensitive. But suppose the description contains the user's full name + bank account: 'Funds from John Doe a/c 1234567890.' That description gets ENCRYPTED with a key from KMS before INSERT, written to the journal as ciphertext. Only application processes with KMS Decrypt permission can read it back.
| Account | Debit | Credit |
|---|---|---|
| Bank Account (1200) | ₦10,000.00 | |
| User Wallet (2000) | ₦10,000.00 |
The journal entry has Bank UP ₦10,000 + User Wallet UP ₦10,000, standard, no encryption needed on the amounts or codes. The DESCRIPTION column stores ciphertext (e.g., `enc::v1::aWdY...`). A DBA running SELECT sees the ciphertext, not the plaintext. The application's read path detects the `enc::` prefix and calls KMS Decrypt to get the cleartext for display.
Takeaway
Application-level encryption (with envelope encryption via KMS) is the right answer for PII-bearing ledger fields. The PATTERN: identify which fields can contain PII (description, metadata sourceEventId, sometimes addresses on cross-border transactions), wrap them with an `enc::<key-version>::<base64-ciphertext>` envelope, store ciphertext in the DB. Keys live in KMS (AWS KMS, GCP KMS, HashiCorp Vault). The DBA has READ access to the DB but NOT to KMS, the principle of least privilege at the right layer. Rotating keys is mechanical: re-encrypt with new key version, mark old version retired. Without this, a database snapshot leaks PII to whoever gets it; with it, the snapshot is useless ciphertext.
Practice this on a real ledger
Reading is half of it. Open this lesson in the lab to post the entries yourself against a real Postgres-backed double-entry ledger, with the validation on. Free, your sandbox is yours.