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
-
On upload: When a document is uploaded through any path (API, web UI, template creation, or signing submission), the
DocumentEncryptorservice encrypts the file before writing it to storage. -
On download: When an authorized user requests a document, the file is decrypted in memory and streamed to the client via the
send_decrypted_filemethod. The plaintext file never touches disk. - 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
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
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 withsupport_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 Derivation Fallback
If the environment variables are not set, DocuTrust derives keys using HKDF (HMAC-based Key Derivation Function) from the application’sSECRET_KEY_BASE. Each key is derived with a unique context string to ensure key separation:
Key Loading Order
Encryption keys are loaded in the initializerconfig/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
TheKeyRotationService supports seamless key rotation with zero downtime:
How Rotation Works
-
Set the new key: Update the environment variable (e.g.,
DOCUMENT_ENCRYPTION_KEY) with the new key value. -
Preserve the old key: Add the previous key to the corresponding
_PREVIOUSenvironment variable (e.g.,DOCUMENT_ENCRYPTION_KEY_PREVIOUS). -
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.
- 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
Re-encryption Process
After deploying with new keys, trigger re-encryption: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.
Security Considerations
Column Width
Encrypted values are significantly larger than their plaintext equivalents. All encrypted database columns use thetext type (unlimited length) rather than fixed-width varchar columns to accommodate the ciphertext, IV, and authentication tag.
JSONB to Text Migration
PostgreSQLjsonb 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:
- S3 — Durable object storage with server-side encryption (SSE-S3 or SSE-KMS)
- Database blob — Fallback for Railway’s ephemeral filesystem
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.