# Document Encryption

> How WPsigner encrypts documents at rest with AES-256-GCM, and how to configure a dedicated encryption key for maximum security.

edition: both
Edition: Lite + Pro
AI note: This page applies to Lite and Pro. Call out Lite limits (PDF only, max 2 signers, email OTP, local timestamp) when they apply. Do not invent Pro-only features.
HTML: https://docs.wpsigner.com/digital-identity/encryption/
Markdown: https://docs.wpsigner.com/md/digital-identity/encryption.md
Source file: digital-identity/encryption.md

---

WPsigner encrypts all uploaded documents and signature images at rest using **AES-256-GCM**, the same encryption standard used by banks and government agencies. This ensures that even if your server's filesystem or database is compromised, your documents remain unreadable.

## How It Works

### Encryption at Rest

When a document is uploaded to WPsigner:

1. **Random filename generated** — A cryptographically random 128-bit filename replaces the original
2. **File encrypted** — AES-256-GCM encrypts the file contents with a unique IV (initialization vector)
3. **Stored securely** — The encrypted file is saved with a `.enc` extension
4. **Decrypted on demand** — Only when an authorized user requests the file

| Layer | Protection |
|-------|-----------|
| **Encryption** | AES-256-GCM (authenticated encryption) |
| **Filename** | Random 128-bit hex (impossible to guess) |
| **Access control** | `.htaccess` blocks direct URL access |
| **Directory listing** | `index.php` prevents browsing |

### What Gets Encrypted

| Content | Encrypted? |
|---------|-----------|
| Uploaded PDF documents | ✅ Yes |
| Signature images | ✅ Yes |
| Completed signed PDFs | Generated on-the-fly |
| Database records | No (metadata only) |

---

## Encryption Key

WPsigner derives its encryption key automatically from your WordPress security salts (`AUTH_KEY` and `SECURE_AUTH_SALT` in `wp-config.php`). This works out of the box, but has one risk: **if those salts are ever regenerated, all encrypted documents become permanently unreadable**.

### Why Define a Dedicated Key

Defining `WPS_ENCRYPTION_KEY` in your `wp-config.php` eliminates this risk entirely:

| Scenario | Without `WPS_ENCRYPTION_KEY` | With `WPS_ENCRYPTION_KEY` |
|----------|------------------------------|---------------------------|
| Normal operation | ✅ Works | ✅ Works |
| WordPress salts rotated | ❌ **Documents lost** | ✅ No impact |
| Security plugin regenerates salts | ❌ **Documents lost** | ✅ No impact |
| Migrate to new server | ⚠️ Must copy exact salts | ✅ Just copy the key |
| Multiple environments | ⚠️ Must sync salts | ✅ Share one key |

> **caution**
WordPress security plugins like **Solid Security (iThemes)**, **Wordfence**, and **SG Security** can regenerate salts with a single click. If you haven't defined `WPS_ENCRYPTION_KEY`, this can make encrypted documents permanently inaccessible.

---

## Setting Up Your Encryption Key

### Step 1: Generate a Key

WPsigner shows a generated key in the admin notice. You can also generate one manually:

**Using WP-CLI:**

```bash
wp eval "echo base64_encode(random_bytes(32));"
```

**Using PHP:**

```php
echo base64_encode(random_bytes(32));
```

This produces a 44-character Base64 string like `a8f3c9d1e6b20a4f7b8c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3g==`.

### Step 2: Add to wp-config.php

Open your `wp-config.php` file and add this line **before** the line that says `/* That's all, stop editing! */`:

```php
define('WPS_ENCRYPTION_KEY', 'your-generated-key-here');
```

### Step 3: Save Your Key

> **important**
**Store your encryption key in a secure location** (password manager, encrypted note, or offline backup). If you lose this key and need to reinstall WordPress, your encrypted documents cannot be recovered.

Recommended backup locations:

- Password manager (1Password, Bitwarden, etc.)
- Encrypted USB drive
- Printed and stored in a safe
- Your hosting provider's secret management

### Step 4: Verify

After adding the key, go to any WPsigner admin page. The security recommendation notice should disappear, confirming that WPsigner is now using your dedicated key.

---

## Key Priority

WPsigner checks for encryption keys in this order:

| Priority | Source | Where it lives |
|----------|--------|---------------|
| 1 (highest) | `WPS_ENCRYPTION_KEY` constant | `wp-config.php` (filesystem) |
| 2 (fallback) | Derived from WordPress salts | `wp-config.php` (filesystem) |

In both cases, the key **never** touches the database. It exists only in `wp-config.php` on the filesystem, protecting against SQL injection attacks.

---

## Salt Mismatch Warning

If WPsigner detects that your WordPress salts have changed (and you haven't defined `WPS_ENCRYPTION_KEY`), it will show a **critical red notice** in the admin:

> 🚨 **WPsigner — Encryption Key Changed**
> WordPress security salts have been regenerated. Previously encrypted documents cannot be decrypted with the current salts.

### How to Recover

1. **Restore original salts** — Find the original `AUTH_KEY` and `SECURE_AUTH_SALT` values from a backup of your `wp-config.php` and restore them
2. **If no backup exists** — The encrypted documents are permanently lost. You can dismiss the notice to reset the key for future documents

### Preventing This Issue

The best prevention is to define `WPS_ENCRYPTION_KEY` **before** any salt rotation occurs. Once defined, salt changes have zero impact on document encryption.

---

## Technical Details

### Cipher Specification

| Parameter | Value |
|-----------|-------|
| **Algorithm** | AES-256-GCM |
| **Key size** | 256 bits (32 bytes) |
| **IV size** | 96 bits (12 bytes, per NIST recommendation) |
| **Authentication tag** | 128 bits (16 bytes) |
| **Key derivation** | HMAC-SHA256 (when using salt fallback) |

### Why AES-256-GCM?

- **Authenticated encryption** — Detects any tampering with encrypted data
- **NIST approved** — Standard for US government classified information
- **Performance** — Hardware-accelerated on modern CPUs (AES-NI)
- **No padding oracle attacks** — GCM mode is immune to these

### File Format

Encrypted files are stored as:

```
Base64( IV (12 bytes) + Auth Tag (16 bytes) + Ciphertext )
```

The IV is generated using PHP's `random_bytes()` (CSPRNG) for each encryption operation, ensuring no two files share an IV.

---

## Frequently Asked Questions

### What happens if I change my WPS_ENCRYPTION_KEY?

All previously encrypted documents will become unreadable. Only change this key if you have no existing encrypted documents, or if you're prepared to lose access to them.

### Can I disable encryption?

Encryption is applied automatically when the OpenSSL extension is available (which is standard on all modern PHP installations). If OpenSSL is not available, files are stored without encryption with a warning in the logs.

### Does encryption affect performance?

Minimally. AES-256-GCM with hardware acceleration (AES-NI) encrypts at several GB/s. For typical documents (1-10MB), the overhead is imperceptible.

### Is the encryption key exposed in the database?

**No.** Whether you use `WPS_ENCRYPTION_KEY` or the salt-derived fallback, the key exists only in `wp-config.php` on the filesystem. A database breach (SQL injection) cannot expose the encryption key.

---

## Next Steps

- [Timestamping (TSA)](/digital-identity/timestamping/) — Add cryptographic timestamps to documents
- [Audit Trails](/digital-identity/audit-trails/) — Understanding the legal record
- [Digital ID](/digital-identity/digital-id/) — Configure your signing certificate
