Recipe: PII storage + encryption design
How Meridian encrypts, stores, and rotates personally identifiable information so that a database breach yields ciphertext only.
Threat model
- Attacker obtains full database dump (rows, columns, indexes).
- Attacker obtains application-server memory for <60 seconds.
- Insider with DB read access attempts lateral movement.
Key hierarchy
KEK (AES-256-GCM) — stored in HSM / KMS, never leaves secure enclave.
DEK (AES-256-GCM) — per-row data encryption key, wrapped by KEK, rotated every 30 days.
Row key — HKDF-SHA512(DEK, row UUID), unique per row, never stored.
Encryption flow
- Generate random 256-bit DEK per customer tenant on creation.
- Wrap DEK with KEK via AES-KWP (NIST SP 800-38F). Store wrapped DEK in
encryption_keystable. - For each PII column, derive row key = HKDF-SHA512(DEK, row_uuid). Encrypt plaintext with AES-256-GCM, 96-bit random IV. Store ciphertext || IV || tag.
- Index searchable fields with blind HMAC-SHA256(DEK, normalized plaintext). Store HMAC in separate indexed column.
Rotation procedure
Every 30 days a cron job creates a new DEK per tenant, re-wraps all row keys, and re-encrypts active PII rows in a background transaction. Old DEK is retained for 7 days to decrypt audit logs, then destroyed.
Access control
Application servers never hold the KEK. Decryption requests flow through an internal sidecar that authenticates via mTLS and enforces rate limits. All decryption events are logged to an append-only audit store.
Compliance mapping
This design satisfies GDPR Art. 32 (encryption at rest), SOC 2 CC6.1 (logical access), and PCI DSS 3.4 (render PAN unreadable).