Security
Encryption Is a Threat-Model Question, Not a Checkbox
Disk, column, and application-level encryption defend against completely different attackers — so "is the data encrypted?" is the wrong question, and "encrypted against whom?" is the one that actually picks the layer.
- Security
- Encryption
- Architecture
- Databases
“Is the data encrypted?” is a comfort question. It gets a yes, everyone relaxes, and a box gets ticked. The question that actually buys you security is “encrypted against whom?” — because disk, column, and application-level encryption defend against completely different attackers, and choosing the wrong layer gives you real cost and a false sense of safety at the same time.
I worked through exactly this trade on a database that encrypted sensitive fields inside the database itself, and the analysis is worth generalizing.
Three layers, three different attackers
- Full-disk / filesystem encryption (LUKS, dm-crypt). Defends against a stolen disk or a copied VM snapshot. The bytes at rest are ciphertext. It does nothing against an attacker with valid database credentials — the running database reads the disk, decrypts transparently, and hands them plaintext, because to the system the data is just there.
- Column / in-database encryption (encrypt specific fields, decrypt on read).
Defends against “attacker gets credentials and runs a broad
SELECT” — now they get ciphertext back. But it carries costs most people don’t price in (below). - Application-tier encryption (encrypt before the data ever reaches the database, keys held in a separate KMS). Defends against the database operator themselves — even an admin with root on the box sees only ciphertext. Strongest separation, most moving parts.
Notice these don’t stack into a ranking. They’re different tools for different adversaries.
The question that picks the layer
State the threat out loud and the layer chooses itself:
- If the requirement is “data at rest must be encrypted” — compliance, lost hardware, a disposed drive — full-disk encryption is the cheap, fast, correct answer, and anything fancier is wasted motion.
- If the requirement is “an operator must not be able to read these values in plaintext” — then disk encryption is useless no matter how much it costs, and you need application-tier crypto with the keys held somewhere the database can’t reach.
Teams build the wrong layer constantly, and almost always because nobody made them say the threat out loud first. “Encrypt the sensitive column” sounds like the thorough choice, but if the actual worry is a lost laptop, you’ve taken on a pile of cost and query limitations to defend against an attacker you weren’t worried about.
The costs of in-database encryption nobody mentions up front
Encrypting a column means the database only ever sees ciphertext for it — and that quietly reshapes what you can do. You lose indexing and range queries on that column; equality on an exact ciphertext is about all you get, and “find everything between X and Y” is gone. A data model that depended on querying that field now has to route around it. That constraint is often a bigger deal than the performance hit.
Two antipatterns that should scare you
I’ve seen both in the wild, and they’re worth naming because they look like security while providing almost none:
- AES in ECB mode for structured data. ECB encrypts identical plaintext blocks to identical ciphertext blocks, so it leaks patterns — the shape of your data survives encryption. If you’re hand-rolling and reach for ECB, stop and use an authenticated mode.
- The key shipped alongside the data. A hardcoded key that lives inside the decryption routine — and therefore travels to every node, every backup, every copy of the schema — defeats encryption against precisely the attacker (credentialed user, operator) that in-database encryption exists to stop. Key custody is the security. If the key rides along with the ciphertext, you’ve encrypted nothing against an insider.
Encryption without key separation is a very elaborate way of storing your data in a slightly different font.
The performance footnote that surprises people
Here’s the counterintuitive part. Full-disk encryption is nearly free on any modern CPU — hardware AES does multiple gigabytes per second per core — and because the operating system’s page cache holds decrypted pages, the crypto only runs on actual disk I/O. A working set that mostly lives in cache barely notices it’s encrypted.
Hand-rolled per-row encryption inside the database is the opposite: it runs on the hot path of every read, and a naive implementation can cost you an order of magnitude in throughput. So the lower, “heavier-sounding” layer is frequently the cheaper one, while the clever in-database approach is the expensive one. Another reason to pick the layer on purpose rather than by vibe.
Name the attacker first
Encryption is an architecture decision, and like the rest of them it’s driven by a threat model, not a checkbox — which is the same argument I keep making about security being part of the design rather than a coat of paint. Decide who you’re defending against, then pick the layer that actually defends against them. “Encrypted” with no answer to “against whom” is just a word that makes an audit happy. If you’re weighing where encryption belongs in your stack, reach out — it’s a fun problem to think through.