Encryption At Rest Patterns

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.

Key Patterns

Single Key Per Volume

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:

Per-File Encryption with a Central Key Management System

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:

  1. Generate a random DEK.
  2. Encrypt the file using the DEK.
  3. Request the KEK from the KMS.
  4. Encrypt the DEK using the KEK.
  5. Store the encrypted file and the encrypted DEK in the metadata.

To decrypt:

  1. Retrieve the encrypted DEK from the metadata.
  2. Request the KEK from the KMS.
  3. Decrypt the DEK using the KEK.
  4. Decrypt the file using the DEK.

Common failure modes:

Transparent Data Encryption in Databases

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:

Key Rotation and Key Lifecycle Management

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:

  1. Generate a new DEK.
  2. Encrypt the new DEK with the current KEK.
  3. Update metadata to include the new DEK and its validity period.
  4. Make the new DEK the default for future data.
  5. Maintain backward compatibility: old data can be decrypted with any of the previous keys.

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:

  1. Read the key_id and valid_from.
  2. Retrieve the key from the KMS.
  3. If the key is valid, decrypt the file; otherwise, try the previous key.
  4. If no key matches, return Key not valid for this period.

Failure modes:

Hybrid Encryption for Large Data Sets

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:

  1. Generate a random DEK.
  2. Encrypt the large data set using the DEK with AES-256-GCM.
  3. Encrypt the DEK with the public key of a recipient (e.g., public_key.pem).
  4. Store the encrypted data and the encrypted DEK in a container or archive.
# 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:

  1. Load the dek-encrypted.rsa.
  2. Decrypt the DEK using the private key.
  3. Use the DEK to decrypt the data.

Failure modes:

Key Derivation and Key Stretching

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:

Cross-Platform Key Sharing

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:

  1. Retrieve the JWK from a service.
  2. Parse the JSON.
  3. Extract the k value and decode it.
  4. Use the key in the application.

Failure modes:

Common Pitfalls and Best Practices

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.