> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spitshake.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Encryption

> How DocuTrust encrypts documents, PII fields, and manages encryption keys.

# Encryption

DocuTrust encrypts data at multiple layers: documents at the storage level, PII fields at the database level, and all communication via TLS. This page covers the technical details of each encryption layer and how keys are managed.

## Document Encryption

All uploaded documents (PDFs, DOCX files, HTML templates) and generated output files (flattened signed PDFs) are encrypted at rest using **AES-256-GCM**.

### How It Works

1. **On upload**: When a document is uploaded through any path (API, web UI, template creation, or signing submission), the `DocumentEncryptor` service encrypts the file before writing it to storage.

2. **On download**: When an authorized user requests a document, the file is decrypted in memory and streamed to the client via the `send_decrypted_file` method. The plaintext file never touches disk.

3. **During processing**: Operations that require access to document contents (PDF flattening, thumbnail generation) decrypt to a secure temporary file, perform the operation, encrypt the output, and securely delete the temporary file.

### Encryption Details

| Property           | Value                                        |
| ------------------ | -------------------------------------------- |
| Algorithm          | AES-256-GCM (Galois/Counter Mode)            |
| Key length         | 256 bits                                     |
| IV/Nonce           | 12 bytes, randomly generated per file        |
| Authentication tag | 128 bits, appended to ciphertext             |
| File format        | `[12-byte IV][ciphertext][16-byte auth tag]` |

GCM mode provides both confidentiality and integrity. Any tampering with the ciphertext or IV is detected during decryption via the authentication tag.

## PII Field Encryption

Submitter PII stored in the PostgreSQL database is encrypted using **Active Record Encryption**, a built-in Rails framework feature.

### Encrypted Fields

| Model     | Field        | Encryption Mode   | Why                                                                                    |
| --------- | ------------ | ----------------- | -------------------------------------------------------------------------------------- |
| Submitter | `email`      | Deterministic     | Enables `find_by(email: ...)` queries while keeping data encrypted at rest.            |
| Submitter | `name`       | Non-deterministic | Maximum privacy. No database-level queries needed on this field.                       |
| Submitter | `phone`      | Non-deterministic | Maximum privacy. No database-level queries needed on this field.                       |
| Submitter | `ip_address` | Non-deterministic | Stored for audit purposes only. No query matching needed.                              |
| Submitter | `user_agent` | Non-deterministic | Stored for audit purposes only. No query matching needed.                              |
| Submitter | `values`     | Non-deterministic | Contains all submitted field values (text entries, signature data, dates, checkboxes). |
| Submitter | `metadata`   | Non-deterministic | Submitter preferences and processing metadata.                                         |

### Deterministic vs. Non-Deterministic

* **Deterministic encryption** produces the same ciphertext for the same plaintext input. This allows the database to perform equality comparisons (e.g., `WHERE email = ?`), but it is slightly less secure because identical plaintext values produce identical ciphertext, making frequency analysis theoretically possible.

* **Non-deterministic encryption** produces unique ciphertext every time, even for the same plaintext. This provides the strongest protection but means the field cannot be used in database queries.

### Compatibility

Active Record Encryption is configured with `support_unencrypted_data = true`, which allows the application to read both encrypted and legacy unencrypted data. This is essential during the migration period when existing records are being encrypted in place.

## Key Management

DocuTrust uses three separate encryption keys, each serving a different purpose:

| Key                       | Environment Variable        | Purpose                                                                          |
| ------------------------- | --------------------------- | -------------------------------------------------------------------------------- |
| Document encryption key   | `DOCUMENT_ENCRYPTION_KEY`   | Encrypts and decrypts document files in storage.                                 |
| Active Record primary key | `AR_ENCRYPTION_PRIMARY_KEY` | Encrypts and decrypts PII fields in the database.                                |
| Config encryption key     | `CONFIG_ENCRYPTION_KEY`     | Encrypts sensitive configuration values (e.g., SMTP passwords, webhook secrets). |

### Key Derivation Fallback

If the environment variables are not set, DocuTrust derives keys using **HKDF** (HMAC-based Key Derivation Function) from the application's `SECRET_KEY_BASE`. Each key is derived with a unique context string to ensure key separation:

```
DOCUMENT_ENCRYPTION_KEY = HKDF(SECRET_KEY_BASE, info: "document-encryption")
AR_ENCRYPTION_PRIMARY_KEY = HKDF(SECRET_KEY_BASE, info: "active-record-encryption")
CONFIG_ENCRYPTION_KEY = HKDF(SECRET_KEY_BASE, info: "config-encryption")
```

<Warning>
  The HKDF fallback is provided for convenience in development and initial deployment. In production, you should set explicit encryption keys via environment variables. This allows you to rotate keys independently without changing `SECRET_KEY_BASE`.
</Warning>

### Key Loading Order

Encryption keys are loaded in the initializer `config/initializers/00_encryption_keys.rb`. The `00_` prefix ensures this file loads before `active_record_encryption.rb` (Rails initializers load alphabetically). This ordering is critical because Active Record Encryption must have access to the keys at initialization time.

## Key Rotation

The `KeyRotationService` supports seamless key rotation with zero downtime:

### How Rotation Works

1. **Set the new key**: Update the environment variable (e.g., `DOCUMENT_ENCRYPTION_KEY`) with the new key value.

2. **Preserve the old key**: Add the previous key to the corresponding `_PREVIOUS` environment variable (e.g., `DOCUMENT_ENCRYPTION_KEY_PREVIOUS`).

3. **Deploy**: On the next deployment, DocuTrust will:
   * Use the **new key** for all new encryption operations.
   * Attempt decryption with the **new key first**, then fall back to the **previous key** if decryption fails.

4. **Re-encrypt**: Run the key rotation service to re-encrypt all existing data with the new key. Once complete, the previous key can be removed.

### Environment Variables for Rotation

| Current Key                 | Previous Key                         |
| --------------------------- | ------------------------------------ |
| `DOCUMENT_ENCRYPTION_KEY`   | `DOCUMENT_ENCRYPTION_KEY_PREVIOUS`   |
| `AR_ENCRYPTION_PRIMARY_KEY` | `AR_ENCRYPTION_PRIMARY_KEY_PREVIOUS` |
| `CONFIG_ENCRYPTION_KEY`     | `CONFIG_ENCRYPTION_KEY_PREVIOUS`     |

### Re-encryption Process

After deploying with new keys, trigger re-encryption:

```bash theme={null}
# Re-encrypt all documents
rails runner "KeyRotationService.rotate_documents!"

# Re-encrypt all PII fields
rails runner "KeyRotationService.rotate_pii_fields!"

# Re-encrypt configuration values
rails runner "KeyRotationService.rotate_config!"
```

<Note>
  Re-encryption is an idempotent operation. If it is interrupted, you can safely run it again. Records already encrypted with the new key will be skipped.
</Note>

## Security Considerations

### Column Width

Encrypted values are significantly larger than their plaintext equivalents. All encrypted database columns use the `text` type (unlimited length) rather than fixed-width `varchar` columns to accommodate the ciphertext, IV, and authentication tag.

### JSONB to Text Migration

PostgreSQL `jsonb` columns cannot store encrypted values (which are opaque strings). Fields like `values` and `metadata` that were originally `jsonb` have been migrated to `text` columns. The application layer handles JSON parsing and serialization in model accessors.

### Object Storage (S3)

Signed documents support dual-write to S3 via ActiveStorage. When configured (`AWS_BUCKET` environment variable), documents are stored in both the database content blob and S3:

1. **S3** — Durable object storage with server-side encryption (SSE-S3 or SSE-KMS)
2. **Database blob** — Fallback for Railway's ephemeral filesystem

The system prefers S3 when available, falls back to the database blob, ensuring documents survive deploys regardless of storage backend. Contact support to initiate a migration of existing documents to S3.

### Temporary Files

When documents must be decrypted for processing (e.g., PDF flattening, thumbnail generation), the plaintext is written to a temporary file with restricted permissions (`0600`). The temporary file is securely deleted immediately after the operation completes, regardless of whether the operation succeeded or failed.
