Part 2 of the Encryption Series. Part 1: Encryption Demystified covers the fundamentals.


“Is the data encrypted at rest?”

It is the most common question in every security questionnaire, every compliance audit, every enterprise customer conversation. And the answer is almost always “yes.”

But here is the question that rarely gets asked:

Encrypted at which layer? And protected against which threat?

We have had multiple customers - running PostgreSQL, MongoDB, and storing files in S3 - ask us to “encrypt the data.” Every time, the conversation that follows reveals that “encryption” means very different things to different people. A DBA thinks filesystem. A security auditor thinks column-level. A compliance officer just needs a checkbox.

This article breaks down what database encryption actually means at each layer, what it protects against, what it does not, and how to make the right choices for your environment.


The Encryption Layers

There are broadly four layers where encryption can happen for data at rest:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
┌─────────────────────────────────────────────┐
│  Layer 4: Application / Column Level        │
│  App encrypts specific fields before INSERT │
├─────────────────────────────────────────────┤
│  Layer 3: Database Engine (TDE)             │
│  DB encrypts data files, WAL, temp files    │
├─────────────────────────────────────────────┤
│  Layer 2: Filesystem / Volume               │
│  LUKS, dm-crypt, EBS encryption             │
├─────────────────────────────────────────────┤
│  Layer 1: Hardware / Disk                   │
│  Self-encrypting drives (SED)               │
└─────────────────────────────────────────────┘

The cover image of this article visualizes exactly this: four stacked layers, each glowing in a distinct color - amber for hardware/disk encryption at the bottom, blue for filesystem/volume encryption, green for database TDE with highlighted table cells, and purple for column-level encryption at the top, with an S3 bucket and key on the side. Each layer adds protection, but none of them protect against everything.


Layer 1: Filesystem and Volume Encryption

This is the most common starting point. Encrypt the entire disk or volume that the database lives on.

Technologies:

  • Linux LUKS/dm-crypt - standard for on-premise Linux servers
  • AWS EBS encryption - transparent, uses KMS keys
  • Azure Disk Encryption - BitLocker underneath
  • GCP Persistent Disk encryption - on by default

What it protects:

  • Physical theft of the drive or server
  • Decommissioned hardware with data still on disk
  • Unauthorized access to raw block devices

What it does NOT protect:

  • An attacker with database credentials - the DB serves data in plaintext to authenticated users
  • A compromised application - if the app can query the DB, it reads plaintext
  • A rogue DBA - they see everything through the SQL interface
  • Backup files - unless the backup destination is also encrypted

This is the layer most people mean when they say “encryption at rest” on a compliance form. It is necessary but not sufficient.

PostgreSQL on LUKS:

1
2
3
4
5
6
7
8
9
# The database files sit on an encrypted volume
# PostgreSQL has no idea encryption exists - it reads/writes normally
# The OS handles encrypt/decrypt transparently

$ lsblk
NAME          TYPE  MOUNTPOINT
sda           disk
└─sda1        part
  └─pg_data   crypt /var/lib/postgresql/data

Layer 2: Database Engine Encryption (TDE)

Transparent Data Encryption operates inside the database engine itself. The database encrypts its own data files, WAL (Write-Ahead Log), and temporary files.

PostgreSQL TDE

PostgreSQL introduced TDE support (via community patches and extensions, with native support maturing in recent versions):

  • Encrypts table data files, indexes, WAL segments, and temp files
  • Uses AES-128 or AES-256
  • Key managed per tablespace or cluster-wide
  • Transparent to applications - no query changes needed
1
2
3
4
5
-- PostgreSQL TDE concept (implementation varies by version/patch)
-- Data files on disk are encrypted
-- Queries return plaintext to authenticated connections
SELECT * FROM customers WHERE id = 42;
-- Returns plaintext - TDE is transparent to the application

Key management:

  • The master encryption key must be provided at database startup
  • If the key is lost, the data is unrecoverable
  • Key rotation requires re-encrypting the data files

MongoDB Encrypted Storage Engine

MongoDB Enterprise (and Percona Server for MongoDB) supports encryption at the storage engine level:

1
2
3
4
5
# mongod.conf
security:
  enableEncryption: true
  encryptionKeyFile: /opt/mongodb/encryption-key
  encryptionCipherMode: AES256-CBC
  • Encrypts all data files, journal files, and indexes
  • AES-256-CBC or AES-256-GCM
  • Supports KMIP for external key management (HashiCorp Vault, AWS KMS)
  • WiredTiger storage engine handles the encryption transparently

What TDE adds over filesystem encryption:

  • Protection against someone copying raw data files without the DB key
  • The DB controls its own encryption - not dependent on the OS layer
  • WAL/journal encryption (filesystem encryption covers this too, but TDE makes it explicit)

What TDE still does NOT protect:

  • Authenticated database users - they see plaintext through queries
  • Network traffic - data travels unencrypted unless TLS is enabled
  • Memory - data is decrypted in RAM during query processing
  • Backups - pg_dump or mongodump output is plaintext unless separately encrypted

Layer 3: Column-Level and Field-Level Encryption

This is where encryption actually protects against a compromised database or a rogue DBA.

The application encrypts specific sensitive fields before writing them to the database. The database stores ciphertext. Even a full database dump reveals nothing for those fields.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Application-layer encryption before INSERT
from cryptography.fernet import Fernet

key = load_encryption_key()  # From Vault, KMS, or secure config
cipher = Fernet(key)

# Encrypt before storing
encrypted_ssn = cipher.encrypt(customer_ssn.encode())
db.execute("INSERT INTO customers (name, ssn) VALUES (%s, %s)",
           [name, encrypted_ssn])

# Decrypt after reading
row = db.execute("SELECT ssn FROM customers WHERE id = %s", [customer_id])
decrypted_ssn = cipher.decrypt(row['ssn']).decode()

The trade-offs are real:

BenefitCost
Protects against DB-level compromiseCannot query encrypted columns (no WHERE, no JOIN, no INDEX)
Protects against rogue DBAsApplication complexity increases
Per-field key rotation possibleKey management becomes critical
Meets strictest compliance requirementsPerformance overhead for encrypt/decrypt

When column-level encryption makes sense:

  • PII fields (Aadhaar numbers, PAN, SSN)
  • Payment card data (PCI-DSS requirement)
  • Health records (HIPAA)
  • Any field where “the DBA should not see this” is a requirement

When it is overkill:

  • Non-sensitive operational data
  • Data that needs to be searchable or joinable
  • Internal systems where the DB admin is trusted

S3 and Object Storage Encryption

Object storage encryption has its own terminology and its own set of trade-offs.

AWS S3 Encryption Options

OptionKey Managed ByEnvelope EncryptionUse Case
SSE-S3AWS (fully managed)Yes, transparentDefault, compliance checkbox
SSE-KMSAWS KMS (your key policy)Yes, auditableRegulatory, key access logging
SSE-CYou (customer-provided)You handle itFull control, you manage keys
Client-sideYou (encrypt before upload)You handle itZero-trust, data never in plaintext on AWS

SSE-S3 is the simplest - AWS manages everything. But AWS also holds the keys, which means AWS (or anyone with AWS access to your account) can theoretically decrypt the data.

SSE-KMS gives you a KMS key that you control the policy for. You can audit who accessed the key, restrict access, and require MFA for decryption. For most enterprise use cases in India, this is the sweet spot.

Client-side encryption means S3 never sees your plaintext. You encrypt before upload, decrypt after download. S3 is just a dumb byte store. This is the only option where a compromised AWS account does not expose your data.

For our customers storing backups in S3, we recommend SSE-KMS as the baseline with client-side encryption for highly sensitive data. The extra complexity of client-side encryption is only justified when the threat model includes a compromised cloud account.


Encryption in Transit for Databases

This is the other half - and the one that gets skipped more often than anyone admits.

PostgreSQL TLS

1
2
3
4
5
# postgresql.conf
ssl = on
ssl_cert_file = '/etc/ssl/certs/server.crt'
ssl_key_file = '/etc/ssl/private/server.key'
ssl_ca_file = '/etc/ssl/certs/ca.crt'
1
2
# pg_hba.conf - REQUIRE TLS for remote connections
hostssl all all 0.0.0.0/0 scram-sha-256

The critical setting is sslmode on the client side:

sslmodeEncryptionServer VerificationProduction Ready?
disableNoNoNever
allowMaybeNoNo
preferIf availableNoNo (silent downgrade)
requireYesNoMinimum acceptable
verify-caYesCA checkGood
verify-fullYesCA + hostnameBest

sslmode=prefer is the default in many PostgreSQL clients. It silently downgrades to unencrypted if the server does not support TLS. In production, enforce verify-full or at minimum require.

MongoDB TLS

1
2
3
4
5
6
# mongod.conf
net:
  tls:
    mode: requireTLS
    certificateKeyFile: /etc/ssl/mongodb.pem
    CAFile: /etc/ssl/ca.pem
1
2
3
4
5
# Client connection with TLS
mongosh "mongodb://host:27017/mydb" \
  --tls \
  --tlsCertificateKeyFile /etc/ssl/client.pem \
  --tlsCAFile /etc/ssl/ca.pem

Replication Traffic

Often forgotten: replication between database nodes is also network traffic. If your primary-to-replica connection is unencrypted, a network tap between them exposes every write.

  • PostgreSQL: replication uses the same TLS configuration as client connections
  • MongoDB: replica set members should use requireTLS for internal communication

Putting It All Together: A Layered Approach

No single encryption layer solves everything. Here is how the layers stack for a typical production deployment:

ThreatFilesystemTDEColumn-LevelTLS
Physical theftYesYesYesN/A
Raw file copyPartialYesYesN/A
DB credential compromiseNoNoYesN/A
Network sniffingN/AN/AN/AYes
Rogue DBANoNoYesN/A
Compromised applicationNoNoNoN/A
Backup exposureNoNoPartialN/A

A practical baseline:

  1. Filesystem encryption (LUKS or cloud provider) - covers physical security
  2. TLS for all connections - covers network security
  3. Column-level encryption for PII - covers compliance and DB-level threats
  4. Encrypted backups - covers backup exposure

For regulated environments (RBI, DPDP Act, PCI-DSS):

  • Add TDE for defense in depth
  • Use SSE-KMS (not SSE-S3) for object storage
  • Implement key rotation schedules
  • Audit key access logs
  • Consider client-side encryption for the most sensitive data classes

A note on quantum computing: AES-256, the workhorse behind database encryption at rest, is quantum-resistant - even a quantum computer running Grover’s algorithm only halves its effective strength to 128-bit equivalent, which remains beyond brute force. TLS 1.3 is already adding hybrid post-quantum key exchange (ML-KEM) to protect data in transit. For database encryption decisions today, quantum is not a factor that changes your choices. We covered the full quantum landscape in Part 1 of this series.


Frequently Asked Questions

Does filesystem encryption protect against SQL injection?

No. Filesystem encryption protects data on disk. SQL injection attacks operate through the application layer - the database decrypts data transparently for any authenticated query. Filesystem encryption is invisible to application-level attacks.

Should I use TDE or filesystem encryption?

Both serve different purposes. Filesystem encryption is simpler and protects the entire volume. TDE gives the database explicit control over its encryption and is often required for compliance certifications. For defense in depth, use both. If you must choose one, filesystem encryption covers more ground with less complexity.

What encryption does S3 use by default?

As of January 2023, all new S3 objects are encrypted by default with SSE-S3 (AES-256, AWS-managed keys). This protects against physical theft of AWS hardware but not against unauthorized access to your AWS account. For stronger controls, use SSE-KMS with a customer-managed key.

Is encrypting the database connection (TLS) really necessary on a private network?

Yes. Private networks are not immune to lateral movement after a breach, ARP spoofing, or compromised network equipment. TLS is cheap, well-supported by every database, and eliminates an entire class of network-level attacks. There is no good reason to skip it in production.

How does MongoDB encrypted storage engine differ from encrypting the filesystem?

Functionally, they are similar - both encrypt data files on disk. The MongoDB encrypted storage engine gives MongoDB direct control over encryption, supports KMIP integration for external key management, and encrypts journal/WAL files explicitly. Filesystem encryption is OS-managed and database-agnostic. For MongoDB Enterprise deployments, the encrypted storage engine is preferred as it integrates with MongoDB’s key management lifecycle.

What about encrypting data in memory?

Data must be decrypted in RAM for query processing - this is true for every database. Technologies like Intel SGX and AMD SEV provide encrypted memory enclaves, but database support is limited and performance overhead is significant. For most environments, focus on disk, network, and application-layer encryption first.


Next in this series: Part 3: Encryption and Licensing - How We Built It - the complete DevOps Genie story with Ed25519 signatures, dual-envelope AES-256-GCM, and the master key trust model.

Previously: Part 1: Encryption Demystified - symmetric, asymmetric, hashing fundamentals.