Protecting data at rest the right way
Encryption at rest protects data on stolen drives, in exposed backups, and in misconfigured cloud storage buckets, but it does not replace application security, since a running application decrypts data for access. This article shows how database, filesystem, and field-level encryption work together and why key management is the actual challenge.
Table of Contents
- 1. The threat model: what encryption at rest actually covers
- 2. What encryption at rest does not protect against
- 3. Filesystem and volume encryption: LUKS, dm-crypt, cloud EBS
- 4. Database encryption: Transparent Data Encryption (TDE)
- 5. Field-level encryption for PII columns
- 6. Key management: the actual hard part
- 7. Magento's crypt key and EncryptorInterface
- 8. Searchability, indexing, and backup encryption
- 9. Encryption layers compared side by side
- 10. Summary
- 11. FAQ
1. The threat model: what encryption at rest actually covers
Encryption at rest protects data while it sits on a physical or virtual storage medium and is not actively being processed by an application. The classic threat scenario is physical theft: a stolen drive from a data center, a decommissioned server resold without secure wiping, or a lost backup tape. Without encryption, all data sits in plaintext the moment someone gains physical access to the storage medium, regardless of any application logic or login form.
A second scenario, increasingly important in the cloud era, is storage misconfiguration: a publicly readable S3 bucket, a misconfigured backup destination, or a snapshot accidentally shared with another tenant. Encryption at rest turns these situations from a catastrophic data leak into an incident with far less damage, because the raw data stays worthless without the matching key. That is exactly why PCI-DSS, GDPR interpretations, and most audit standards require encryption for stored personal and payment-related data, even when additional controls like access management already exist.
2. What encryption at rest does not protect against
The most common misconception in security audits is assuming encrypted data is automatically protected against application-level attacks. That's not true: as soon as an application is running, it must decrypt the data to process it, and it's precisely in that decrypted state that SQL injection, compromised admin credentials, or an exploited remote code execution vulnerability strike. An attacker running direct database queries via SQL injection receives results just as decrypted as the application itself, since the database transparently decrypts for any authenticated access.
A compromised application server with a valid database connection also bypasses encryption at rest entirely, because the connection itself triggers decryption. The same applies to insider threats with legitimate database access and to backups that end up unencrypted in a test or staging environment during a routine restore. Encryption at rest is therefore a complement to, not a replacement for, input validation, least-privilege access controls, network segmentation, and the other OWASP controls that defend running systems against attacks.
3. Filesystem and volume encryption: LUKS, dm-crypt, cloud EBS
Volume encryption operates below the filesystem and encrypts every block written to disk, fully transparent to applications and the database. On Linux, LUKS with dm-crypt is the standard: once unlocked at boot, the volume behaves like a normal block device, while in a powered-off state or on a stolen drive all contents remain unreadable. In the cloud, managed offerings like AWS EBS encryption, Azure Disk Encryption, or Google Persistent Disk Encryption serve the same function, usually enabled with a checkbox and no measurable performance loss on modern CPUs with AES-NI.
The big advantage of volume encryption is simplicity: it protects everything on the drive equally, including config files, logs, and temporary files, without touching application code. The downside is just as fundamental: root access on a running, unlocked system sees all data in plaintext, and a database export or backup that leaves the volume is no longer protected by this layer. Volume encryption is the right baseline defense against physical theft, but no solution for selectively protecting individual sensitive fields.
#!/usr/bin/env bash
# setup-luks-volume.sh - Create and mount a LUKS-encrypted data volume
set -euo pipefail
DEVICE="/dev/sdb1"
MAPPER_NAME="encrypted_data"
MOUNT_POINT="/var/lib/mysql-encrypted"
# Initialize LUKS2 header with AES-XTS and a strong key derivation function
cryptsetup luksFormat --type luks2 \
--cipher aes-xts-plain64 \
--key-size 512 \
--hash sha256 \
--pbkdf argon2id \
"$DEVICE"
# Open the encrypted volume, prompts for passphrase or reads from a keyfile
cryptsetup luksOpen "$DEVICE" "$MAPPER_NAME" --key-file /etc/luks/db.key
# Format and mount the now-unlocked block device like any other filesystem
mkfs.ext4 "/dev/mapper/${MAPPER_NAME}"
mkdir -p "$MOUNT_POINT"
mount "/dev/mapper/${MAPPER_NAME}" "$MOUNT_POINT"
# Register in /etc/crypttab for automatic unlock at boot via keyfile
echo "${MAPPER_NAME} UUID=$(blkid -s UUID -o value "$DEVICE") /etc/luks/db.key luks" >> /etc/crypttab
4. Database encryption: Transparent Data Encryption (TDE)
Transparent Data Encryption (TDE) encrypts data files, redo logs, and backups at the database engine level, without requiring any changes to application code or SQL queries. MySQL and MariaDB offer this through the InnoDB tablespace encryption plugin: tablespaces are encrypted with a master key, which in turn is managed via a key management plugin, a local keyring file, or an external KMS. The advantage over plain volume encryption is more granular control: TDE can be enabled per tablespace and additionally protects backups created with compatible tools, while a raw dd image of the drive would still be unencrypted if only TDE is active without volume encryption.
TDE does not, however, solve the problem of authenticated database access: any query the database normally answers returns decrypted values, because the engine transparently decrypts for the application process. Anyone who needs targeted protection for individual highly sensitive columns like national ID numbers or payment data typically combines TDE with field-level encryption for exactly those columns. In practice, TDE is the right default protection for the entire database against backup theft and physical access, while field-level encryption adds extra control for the most critical data points.
5. Field-level encryption for PII columns
With field-level encryption, the application selectively encrypts individual columns before writing them to the database and only decrypts them at the actual read point in application code. That keeps these values unreadable even for a database administrator with full SQL access, unless they also hold the application key, a considerably stronger protection level than TDE for the affected columns. Typical candidates are national ID numbers, bank details, health data, or other fields with particularly high sensitivity, while less critical columns like product names or order status can remain unencrypted.
For PHP implementations, AES-256-GCM with authenticated encryption is the current standard, available via both OpenSSL and libsodium. The key requirement is a freshly generated nonce for every encryption operation, never a reused or predictable one, since nonce reuse under GCM can defeat the entire authenticity guarantee. GCM's authentication tag also detects any subsequent tampering with the ciphertext, making it more robust than plain confidentiality modes like CBC without a separate HMAC.
<?php
declare(strict_types=1);
namespace Mironsoft\PiiEncryption\Model;
/**
* Field-level encryption for highly sensitive PII columns using AES-256-GCM.
* Uses a fresh random nonce per operation and an authentication tag to detect tampering.
*/
final class FieldEncryptor
{
private const CIPHER = 'aes-256-gcm';
private const NONCE_LENGTH = 12; // 96-bit nonce recommended for GCM
public function __construct(
private readonly string $keyBinary // 32-byte key, loaded from a KMS or secrets manager
) {
}
/**
* Encrypts a plaintext PII value and returns nonce, tag and ciphertext packed together.
*
* @param string $plaintext Sensitive value to encrypt, e.g. a national ID number
* @return string Base64-encoded payload: nonce || tag || ciphertext
*/
public function encrypt(string $plaintext): string
{
$nonce = random_bytes(self::NONCE_LENGTH); // never reuse a nonce with the same key
$tag = '';
$ciphertext = openssl_encrypt(
$plaintext,
self::CIPHER,
$this->keyBinary,
OPENSSL_RAW_DATA,
$nonce,
$tag
);
if ($ciphertext === false) {
throw new \RuntimeException('Field encryption failed.');
}
return base64_encode($nonce . $tag . $ciphertext);
}
/**
* Decrypts a previously encrypted field value and verifies the authentication tag.
*
* @param string $payload Base64-encoded nonce || tag || ciphertext
* @return string The original plaintext value
*/
public function decrypt(string $payload): string
{
$raw = base64_decode($payload, true);
$nonce = substr($raw, 0, self::NONCE_LENGTH);
$tag = substr($raw, self::NONCE_LENGTH, 16); // GCM tag is 16 bytes
$ciphertext = substr($raw, self::NONCE_LENGTH + 16);
$plaintext = openssl_decrypt(
$ciphertext,
self::CIPHER,
$this->keyBinary,
OPENSSL_RAW_DATA,
$nonce,
$tag
);
if ($plaintext === false) {
// Tag mismatch means the ciphertext was tampered with or the key is wrong
throw new \RuntimeException('Field decryption failed: authentication tag mismatch.');
}
return $plaintext;
}
}
6. Key management: the actual hard part
Encryption algorithms themselves have been solved for years; the genuinely hard problem is key management: where keys live, who can access them, how they get rotated, and what happens when a key is compromised. A key stored right next to the encrypted data in the same database or the same application code offers no real protection, because an attacker with database access gets both at once. The basic rule is therefore: keys and encrypted data must live in separate trust zones, ideally with different access paths and different logging.
For production systems, dedicated key management systems like AWS KMS, HashiCorp Vault, or a Hardware Security Module (HSM) are the right approach. They handle key generation, automatic rotation on a defined schedule, access logging, and the ability to revoke a key instantly, all without ever exporting the raw key value from secure storage. Envelope encryption is the dominant pattern here: a data encryption key encrypts the actual data, while an overarching key encryption key in the KMS protects that data encryption key. Rotating the key encryption key then requires no re-encryption of all the data, only re-encryption of the much smaller data encryption keys.
{
"keyRotationPolicy": {
"keyId": "arn:aws:kms:eu-central-1:123456789012:key/pii-field-encryption",
"rotationEnabled": true,
"rotationIntervalDays": 90,
"envelopeEncryption": {
"keyEncryptionKey": "kms-managed",
"dataEncryptionKeyAlgorithm": "AES-256-GCM",
"dataEncryptionKeyCacheTtlSeconds": 300
},
"accessPolicy": {
"allowedPrincipals": [
"role/magento-app-encrypt-service"
],
"requireMfaForDecrypt": false,
"requireMfaForKeyDeletion": true,
"auditLogging": "cloudtrail-enabled"
},
"keyDeletionWindowDays": 30
}
}
7. Magento's crypt key and EncryptorInterface
Magento ships its own encryption model built around a crypt key stored in app/etc/env.php. This key is generated during installation and used by Magento\Framework\Encryption\EncryptorInterface for all sensitive configuration values, such as API credentials in core_config_data, payment gateway credentials, and certain customer values. Internally, the encryptor uses AES-256-GCM or older HMAC-secured modes depending on the Magento version, though every production Magento 2.4.x installation uses authenticated encryption by default. Operationally critical: if the crypt key is lost or swapped without a prior re-encryption pass, every configuration value encrypted with it becomes unreadable, which is why it must always be part of any backup strategy, secured separately from the actual database.
For custom modules, the encryptor is the right tool for protecting additional sensitive fields instead of building a custom cryptography implementation. The class correctly handles nonce generation, padding, and key versioning, and Encryptor::decrypt() also supports older encryption versions after a crypt key rotation. Anyone using raw openssl_encrypt() without version tracking instead risks undecryptable legacy data after a key rotation, because there's no longer any record of which key originally encrypted which value.
<?php
declare(strict_types=1);
namespace Mironsoft\CustomerVault\Model;
use Magento\Framework\Encryption\EncryptorInterface;
/**
* Encrypts and decrypts a sensitive customer attribute using Magento's own
* crypt key infrastructure instead of a custom implementation.
*/
final class SensitiveAttributeHandler
{
public function __construct(
private readonly EncryptorInterface $encryptor
) {
}
/**
* Encrypts a sensitive value before persisting it, e.g. a national tax ID.
*
* @param string $rawValue Plaintext value entered by the customer
* @return string Encrypted value ready for storage in a custom attribute
*/
public function encryptForStorage(string $rawValue): string
{
// Uses the crypt key from app/etc/env.php with versioned, authenticated encryption
return $this->encryptor->encrypt($rawValue);
}
/**
* Decrypts a stored value for display, honoring old key versions after rotation.
*
* @param string $storedValue Encrypted value as read from the database
* @return string Decrypted plaintext value
*/
public function decryptForDisplay(string $storedValue): string
{
// decrypt() transparently handles values encrypted with a prior key version
return $this->encryptor->decrypt($storedValue);
}
}
8. Searchability, indexing, and backup encryption
Field-level encryption has a significant practical downside: encrypted columns are no longer searchable or sortable with standard SQL, because the same plaintext produces a different ciphertext every time when nonces are used correctly. WHERE email = 'x@y.com' no longer works against an encrypted email column. Common solutions are an additional deterministic HMAC index for exact-match lookups, a separate search index outside the database, or deliberately skipping encryption for fields that must remain searchable, offset by stronger access controls instead. This tradeoff should be made per project and documented, not implicitly decided by whichever implementation gets built first.
Backups deserve the same rigor as the live database, but are frequently neglected in practice. A mysqldump that lands unencrypted on a network share or in a misconfigured cloud bucket defeats every layer of database encryption entirely. The correct pipeline encrypts the dump right at creation time, before it ever leaves the source system, and uses a dedicated backup key separate from the database key, so a compromised application key doesn't automatically expose every historical backup as well.
#!/usr/bin/env bash
# encrypted-backup.sh - Dump the Magento database and encrypt it before it leaves the host
set -euo pipefail
DB_NAME="magento"
BACKUP_DIR="/var/backups/magento"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
GPG_RECIPIENT="backups@mironsoft.de"
mkdir -p "$BACKUP_DIR"
# Stream the dump directly into gpg, plaintext data never touches disk unencrypted
mysqldump --single-transaction --quick --routines "$DB_NAME" \
| gzip -9 \
| gpg --encrypt --recipient "$GPG_RECIPIENT" --trust-model always \
> "${BACKUP_DIR}/${DB_NAME}-${TIMESTAMP}.sql.gz.gpg"
# Verify the encrypted archive is well-formed before considering the backup successful
gpg --list-packets "${BACKUP_DIR}/${DB_NAME}-${TIMESTAMP}.sql.gz.gpg" > /dev/null
echo "[OK] Encrypted backup written: ${DB_NAME}-${TIMESTAMP}.sql.gz.gpg"
9. Encryption layers compared side by side
The three encryption layers protect against different attack scenarios and combine sensibly rather than compete with each other. The table below shows what each layer covers and where it hits its limits.
| Layer | Protects against | Does not protect against | Recommended use |
|---|---|---|---|
| Volume/LUKS | Physical drive theft | Access to a running, unlocked system | Baseline protection for every server, no exceptions |
| DB-Level TDE | Stolen backups, tablespace files | Authenticated SQL queries, SQL injection | Default protection for the entire database |
| Field-Level | DBA access, compromised DB credentials | A compromised app server holding decryption rights | Use selectively for highly sensitive PII columns only |
| Magento Crypt Key | Configuration values, API credentials | Application vulnerabilities like XSS or RCE | Always via EncryptorInterface, never custom crypto |
| Unencrypted backup | Nothing | Defeats every other layer if the backup is stolen | Never without separate backup encryption |
Mironsoft
Data security, key management, and encryption concepts for Magento stores
Ready to secure your data at rest?
We review your encryption approach for the database, backups, and PII fields, set up key management correctly, and implement field-level encryption exactly where it actually makes a difference.
Encryption audit
Analysis of existing volume, database, and field encryption
Key management setup
Building KMS/Vault integration, rotation, and envelope encryption
PII field encryption
AES-256-GCM implementation for sensitive customer attributes
10. Summary
Data Encryption at Rest solves a clearly bounded problem: protection against physical theft, lost or stolen backups, and misconfigured storage environments. Volume encryption with LUKS or cloud EBS encryption is the right baseline for every server, TDE at the database level adds granular protection for tablespaces and backups, and field-level encryption with AES-256-GCM specifically protects the most critical PII columns even from database administrators. None of these layers replace application security, since a running application inevitably decrypts data to process it.
The actual hard part isn't the cryptography itself, it's key management: separate trust zones for keys and data, automatic rotation, envelope encryption, and a dedicated solution like AWS KMS or HashiCorp Vault instead of homegrown key handling. Magento operators should treat the crypt key in app/etc/env.php as a critical secret at all times, use EncryptorInterface for their own sensitive fields, and always encrypt backups before they leave the source system.
Data Encryption at Rest - The Essentials at a Glance
Threat model
Protects against physical theft and backup exposure, not against SQL injection or compromised app credentials.
Combine three layers
LUKS/cloud volume encryption as the baseline, TDE for the database, field-level encryption for highly sensitive PII columns.
Key management is the core
Separate trust zones, rotation, and envelope encryption via KMS/Vault instead of homegrown key handling.
Magento crypt key
Use EncryptorInterface instead of custom cryptography, treat the crypt key in app/etc/env.php as a critical secret.