# How TREK Handles Encryption at Rest and ENCRYPTION_KEY Rotation

> Discover how TREK secures sensitive data with AES-256-GCM encryption at rest and learn about seamless ENCRYPTION_KEY rotation via migration scripts with no service interruption.

- Repository: [Maurice/TREK](https://github.com/mauriceboe/TREK)
- Tags: how-to-guide
- Published: 2026-07-11

---

**TREK encrypts sensitive data using AES-256-GCM with keys derived from the ENCRYPTION_KEY environment variable, and provides a dedicated migration script to rotate encryption keys without service interruption or data loss.**

TREK is an open-source application that stores API keys, MFA secrets, and SMTP credentials encrypted on disk rather than in plaintext. Understanding the encryption mechanism and the proper procedure for rotating the `ENCRYPTION_KEY` is essential for maintaining security compliance and responding to potential credential compromises.

## How TREK Encrypts Data at Rest

TREK implements **AES-256-GCM** encryption for all sensitive data stored on disk. The system relies on a single master secret provided via the `ENCRYPTION_KEY` environment variable, which should be a cryptographically secure 32-byte hex string generated with `openssl rand -hex 32` [README.md#L77-L79](https://github.com/mauriceboe/TREK/blob/main/README.md#L77-L79).

When the server starts, it reads `ENCRYPTION_KEY` from `process.env.ENCRYPTION_KEY` and initializes purpose-specific encryption helpers. If the variable is unset, TREK falls back to a legacy `.jwt_secret` file or auto-generates a temporary key, but production deployments must always supply a strong, persistent key.

### Purpose-Bound Key Derivation

Rather than using the raw `ENCRYPTION_KEY` directly, TREK derives separate keys for different data types using SHA-256 hashing with domain separation. This ensures that API keys and MFA secrets use cryptographically distinct keys even when derived from the same master secret.

In [`server/scripts/migrate-encryption.ts`](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts), the derivation functions are implemented as follows [server/scripts/migrate-encryption.ts#L31-L37](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts#L31-L37):

```typescript
function apiKey(encryptionKey: string) {
  return crypto.createHash('sha256')
               .update(`${encryptionKey}:api_keys:v1`).digest();
}

function mfaKey(encryptionKey: string) {
  return crypto.createHash('sha256')
               .update(`${encryptionKey}:mfa:v1`).digest();
}

```

The production runtime uses identical logic in [`server/apiKeyCrypto.ts`](https://github.com/mauriceboe/TREK/blob/main/server/apiKeyCrypto.ts) and [`server/mfaCrypto.ts`](https://github.com/mauriceboe/TREK/blob/main/server/mfaCrypto.ts) to ensure consistent encryption across the application.

### The Encryption Process

When encrypting data, TREK generates a random 12-byte IV (Initialization Vector) for each value and applies AES-256-GCM encryption using the derived purpose-specific key. Encrypted API key values are prefixed with `enc:v1:` to allow the runtime to distinguish encrypted from legacy plaintext values [server/scripts/migrate-encryption.ts#L29-L47](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts#L29-L47).

The decryption process reverses this workflow: the runtime detects the `enc:v1:` prefix, extracts the IV and ciphertext, and decrypts using the appropriate derived key. This transparent encryption/decryption occurs on every database read and write, ensuring data remains encrypted at rest while remaining accessible to the application.

## How to Rotate the ENCRYPTION_KEY

Rotating encryption keys is a critical security practice when personnel leave, keys are potentially exposed, or as part of routine security policy updates. TREK provides a dedicated migration script that safely re-encrypts all secrets with a new key while maintaining data integrity.

### Pre-Migration Safety Measures

The migration script prioritizes data safety by creating a timestamped backup of the SQLite database before modifying any records. This ensures you can recover immediately if the rotation process encounters unexpected errors [server/scripts/migrate-encryption.ts#L82-L86](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts#L82-L86).

### Step-by-Step Key Rotation Process

The rotation workflow is interactive and designed to prevent accidental key exposure in shell history. Execute the migration inside your running container [README.md#L20-L28](https://github.com/mauriceboe/TREK/blob/main/README.md#L20-L28):

```bash
docker exec -it trek node --import tsx scripts/migrate-encryption.ts

```

The script executes the following sequence [server/scripts/migrate-encryption.ts#L58-L61](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts#L58-L61):

1. **Prompts for the old ENCRYPTION_KEY** – Input is masked to prevent display in terminal logs
2. **Prompts for the new ENCRYPTION_KEY** – Similarly masked for security
3. **Requires explicit confirmation** – You must type "yes" to proceed after reviewing the backup location
4. **Migrates all secrets** – Decrypts each value with the old key and re-encrypts with the new key, handling API keys and MFA secrets separately
5. **Reports statistics** – Displays counts of migrated, already-migrated, skipped, and errored items

After completion, restart the container with the new `ENCRYPTION_KEY` environment variable. All subsequent database operations will use the new key, and the old key can be securely deleted from your secrets manager.

## Configuration Examples

Generate a production-grade encryption key and start TREK with proper environment configuration:

```bash

# Generate a fresh 32-byte hex key

ENCRYPTION_KEY=$(openssl rand -hex 32)

# Start TREK with the encryption key (Docker example)

docker run -d -p 3000:3000 \
  -e ENCRYPTION_KEY=$ENCRYPTION_KEY \
  -v ./data:/app/data \
  -v ./uploads:/app/uploads \
  mauriceboe/trek

```

For Docker Compose deployments, define the variable in your environment block [docker-compose.yml#L36-L39](https://github.com/mauriceboe/TREK/blob/main/docker-compose.yml#L36-L39):

```yaml
services:
  trek:
    environment:
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}

```

## Key Implementation Files

- **[`server/scripts/migrate-encryption.ts`](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts)** – The migration script that re-encrypts all secrets and demonstrates the crypto implementation [migrate-encryption.ts](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts)
- **[`server/apiKeyCrypto.ts`](https://github.com/mauriceboe/TREK/blob/main/server/apiKeyCrypto.ts)** – Production runtime helpers for API key encryption/decryption
- **[`server/mfaCrypto.ts`](https://github.com/mauriceboe/TREK/blob/main/server/mfaCrypto.ts)** – Production runtime helpers for MFA secret encryption
- **[`README.md`](https://github.com/mauriceboe/TREK/blob/main/README.md)** – Documentation for `ENCRYPTION_KEY` setup and rotation procedures [README.md#L77-L104](https://github.com/mauriceboe/TREK/blob/main/README.md#L77-L104)
- **[`docker-compose.yml`](https://github.com/mauriceboe/TREK/blob/main/docker-compose.yml)** – Example configuration showing environment variable exposure [docker-compose.yml#L36-L39](https://github.com/mauriceboe/TREK/blob/main/docker-compose.yml#L36-L39)

## Summary

- **TREK uses AES-256-GCM** encryption with per-purpose keys derived from the `ENCRYPTION_KEY` environment variable to secure API keys and MFA secrets at rest.
- **Key derivation** uses SHA-256 with domain-specific strings (`api_keys:v1`, `mfa:v1`) to ensure cryptographic separation between different secret types.
- **Encrypted values** are prefixed with `enc:v1:` to enable the runtime to distinguish between encrypted and legacy plaintext data.
- **Key rotation** is performed using [`server/scripts/migrate-encryption.ts`](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts), which creates a database backup and interactively migrates all secrets from the old key to the new key.
- **Production deployments** should always explicitly set `ENCRYPTION_KEY` to a 32-byte hex value generated via `openssl rand -hex 32` rather than relying on auto-generated keys.

## Frequently Asked Questions

### What happens if I lose my ENCRYPTION_KEY?

If you lose the `ENCRYPTION_KEY`, encrypted data cannot be recovered. TREK cannot decrypt API keys or MFA secrets without the original key, as there is no backdoor or key escrow mechanism. Always store your encryption key in a secure secrets manager and maintain offline backups.

### Can I rotate the ENCRYPTION_KEY without downtime?

While the migration script itself requires the application to be running to access the database, the rotation process does not require an extended maintenance window. The script completes quickly for typical workloads, and you only need to restart the container after migration to switch to the new key. For high-availability deployments, consider running the migration on a single instance while others remain active, then rolling the key update across all instances.

### How do I verify the migration succeeded?

The migration script outputs statistics showing the number of records migrated, skipped, and errored. After restarting with the new `ENCRYPTION_KEY`, verify functionality by testing API key authentication and MFA login flows. If any issues occur, restore from the timestamped backup created by the script and verify you entered the correct old key during migration.

### Is the legacy JWT_SECRET still supported?

TREK maintains backward compatibility with a legacy `.jwt_secret` file for environments that have not yet migrated to `ENCRYPTION_KEY`. However, this fallback is deprecated and should not be used in production. The `ENCRYPTION_KEY` variable provides superior security and explicit control over your encryption material.