Production engineering guide for encryption at rest patterns covering patterns, implementation strategies, and operational best practices.
Encryption at rest protects data stored on disk or in a database, ensuring that even if storage media is physically removed or stolen, the data remains confidential. It matters when data is persisted across reboots, migrations, backups, or replication, and when confidentiality is a non-negotiable requirement—such as in financial records, health information, or user profiles. This page details the patterns engineers use to implement encryption at rest across systems, focusing on what must be done, what can go wrong, and how to get it right.
Encrypt an entire storage volume using a single key derived from a master key or a key encryption key (KEK). This pattern is common for block devices (e.g., LVM, ZFS, or raw disk) and is typically implemented with the Linux cryptsetup tool.
sudo cryptsetup luksFormat /dev/sdb1 --cipher=aes-xts-plain64 --key-size=256 --hash=sha256
# Open the volume
sudo cryptsetup open /dev/sdb1 encrypted_data --key-file=/etc/keys/luks-keyfile
# Mount the volume
sudo mount /dev/mapper/encrypted_data /mnt/data
The luksFormat command sets up a LUKS header containing metadata: version, cipher, key size, hash, and key slots. Each key slot holds a key that can unlock the volume. Key files are typically stored in /etc/keys/ and are used during boot or service startup.
Key failure modes:
cryptsetup open error: Failed to open device encrypted_data: No such file or directory.Invalid key file format.Key slot 0: Key not valid after a key rotation.keyring), causing a stale key and failed encryption on reboot.Encrypt individual files or database pages using a single key derived from a centralized key management service (KMS). This pattern is used when different files require different keys, or when fine-grained access control is needed.
Each file is encrypted with a data encryption key (DEK), which is then encrypted with a key encryption key (KEK) from the KMS. The DEK is stored alongside the encrypted data.
{
"file_id": "user-12345-profile.json",
"encrypted_data": "base64-encoded-ciphertext",
"dek_ciphertext": "base64-encoded-encrypted-dek",
"dek_id": "dek-7a3f9d81-4b2c-4e1d-8f3a-9c2b1d4f5a8e",
"encryption_algorithm": "AES-256-GCM",
"iv": "a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0"
}
To encrypt a file:
To decrypt:
Common failure modes:
HTTP 503 Service Unavailable from the KMS endpoint.DEK mismatch: expected dek-7a3f9d81, got dek-3b2c1d4f.Key not found: dek-7a3f9d81, or Invalid key version: 2, expected 1.Invalid DEK: expected 32-byte key, got 16 bytes.Encrypt data at the storage layer of a relational database, so that all data on disk is encrypted without application changes. This is implemented in PostgreSQL using pgcrypto, in MySQL with InnoDB tablespace encryption, and in SQLite with SQLCipher.
In PostgreSQL, enable TDE with pgcrypto and pg_encrypted_table.
-- Create an encrypted tablespace
CREATE TABLESPACE encrypted_tablespace LOCATION '/mnt/encrypted-data';
-- Create a table in the encrypted tablespace
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT,
email TEXT ENCRYPTED
) TABLESPACE encrypted_tablespace;
-- Encrypt a column using a specific key
ALTER TABLE users ADD COLUMN ssn VARCHAR(11) ENCRYPTED USING 'AES-256-CBC';
The database encrypts each page (typically 8 KB) on disk, and each page is encrypted with a unique key derived from a master key and a page ID. The master key is stored in a separate table or in the database’s pg_encrypted_keys catalog.
Key failure modes:
ERROR: key not found in pg_encrypted_keys (key_id = 1) during query execution.ERROR: page 42 not in buffer, not encrypted.ERROR: function pg_encrypt(bytea, bytea, text) does not exist.ENCRYPTED but the encryption is not applied to existing data: SELECT pg_encrypted_table_count() = 0 after ALTER TABLE.Rotate keys regularly to reduce exposure and enable revocation. This pattern involves maintaining multiple keys (current, previous, historical) and updating metadata to track which key was used for which data.
The rotation process:
To support backward compatibility, include a key_id and valid_from timestamp in the metadata.
{
"key_id": "dek-7a3f9d81-4b2c-4e1d-8f3a-9c2b1d4f5a8e",
"valid_from": "2024-03-01T00:00:00Z",
"valid_to": "2024-09-01T00:00:00Z",
"dek_ciphertext": "base64-encoded-encrypted-dek",
"version": 2
}
When a file is decrypted:
key_id and valid_from.Key not valid for this period.Failure modes:
Key not found: dek-7a3f9d81 even though the key exists.Invalid key ID: dek-5a6b7c8d.valid_from timestamp is missing or malformed: Error parsing timestamp: "2024-03-01T00:00:00".When encrypting large data sets (e.g., video files, backup archives), use hybrid encryption: encrypt the data with a symmetric algorithm, then encrypt the symmetric key with an asymmetric algorithm.
This pattern is used when:
Workflow:
public_key.pem).# Encrypt a large file with hybrid encryption
openssl enc -aes-256-gcm -in large-video.mp4 -out encrypted-video.gcm -pass file:<(openssl rand -base64 32)
# Encrypt the DEK with RSA public key
echo "encrypted-video.gcm" | openssl rsautl -encrypt -pubin -inkey public_key.pem -out dek-encrypted.rsa
To decrypt:
dek-encrypted.rsa.Failure modes:
Error: RSA block size mismatch.Decrypted DEK: expected 32 bytes, got 16.GCM tag mismatch: expected 16 bytes, got 12.Invalid header: expected "ENC", got "ENK".When keys are derived from passwords or low-entropy inputs, use key derivation functions (KDFs) to increase security and resistance to brute-force attacks.
Commonly used KDFs: PBKDF2, scrypt, and Argon2.
Example using pbkdf2 with openssl:
# Derive a key from a password
echo "my-secret-password" | \
openssl pbkdf2 \
-in /dev/stdin \
-out derived-key.bin \
-iter 100000 \
-keysize 32 \
-salt 16 \
-prf hmac-sha256
The resulting derived-key.bin is 32 bytes long and can be used as a DEK or KEK.
Failure modes:
PBKDF2: wrong salt length.Key derivation took 1.2 seconds, expected >5 seconds.Unknown PRF: hmac-sha512.Key size mismatch: expected 32 bytes, got 31.When multiple systems (e.g., microservices, mobile apps, IoT devices) need to share encryption keys, use standard formats like JSON Web Key (JWK) or PKCS#8.
{
"kty": "oct",
"kid": "key-123",
"use": "enc",
"alg": "A256GCM",
"k": "base64url-encoded-32-byte-key"
}
To import a key:
k value and decode it.Failure modes:
k value is not base64url-encoded: Invalid base64: unexpected character ' '.alg field is missing or invalid: Unsupported algorithm: A128CBC.kid is not unique: Key with ID "key-123" already exists.use field is not properly set: Key usage mismatch: expected "enc", got "sig".dek-7a3f9d81 is better than key_7a3f9d81 or dek-7a3f9d81-2024-03-01.The success of encryption at rest is not measured by encryption speed or algorithm choice, but by the ability to decrypt data in the future, even after the original systems are gone.
This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy