Press enter or click to view image in full size
Introduction
One of the key shortcomings in GoPhish is that, aside from user account passwords (which are properly hashed), the rest of the sensitive data is stored in plaintext. If an attacker gains access to the SQLite or MySQL database —whether through a server breach, an exposed backup, or an insider threat — all of this information becomes immediately readable.
This includes SMTP and IMAP credentials, as well as the captured data of simulation targets. In the Anglerphish fork, this also extends to SMS provider API keys.
Anglerphish addresses this by introducing optional application-layer AES-256-GCM encryption for all sensitive fields. This article covers the design, what it protects, how to use it, and a quick deployment checklist.
What Gets Encrypted in Anglerphish
Encryption covers four categories of sensitive data stored in the database:
SMTP (Sending Profiles)
password: SMTP server password
SMS (SMS Profiles)
provider_config: Twilio Account SID/Auth Token, Vonage API Key/Secret
IMAP Configuration
password: IMAP server password
Events
details: captured details from phishing targets (not only credentials)
The last category is the most critical. During a phishing simulation, when a target submits their credentials on a landing page, those credentials end up in the events table (along with other metadata like IP addresses). Without encryption, anyone with database access can view all captured information in plaintext.
Design Decisions
Why AES-256-GCM?
AES-256-GCM provides both confidentiality and integrity (authenticated encryption) and is widely considered a modern standard. It is natively supported by Go’s standard library, requiring no external dependencies. It is also highly performant, making encryption and decryption effectively negligible for small data such as credentials and API keys. However, it must be used carefully, particularly with regard to nonce uniqueness.
Each encrypted value uses a unique nonce, stored alongside the ciphertext.
Opt-In via Environment Variable
Encryption is activated by setting a single environment variable:
ANGLERPHISH_ENCRYPTION_KEY=<your-base64url-encoded-key>If the variable isn’t set, the application works exactly as before — plaintext in, plaintext out. No breaking changes, no forced migrations. This was a deliberate choice: encryption should enhance security without disrupting existing deployments.
Versioned Encrypted Format
Every encrypted value is stored with a recognizable prefix:
ENC:v1:<base64 nonce>:<base64 ciphertext>This gives us several advantages:
- Detection — The system can immediately determine whether a value is encrypted or plaintext by checking for the
ENC:v1:prefix. - Coexistence — Encrypted and plaintext values can exist side-by-side during gradual migration.
- Future-proofing — New formats (e.g.,
ENC:v2:) can be introduced without breaking existing data.
Transparent to the Application
Encryption and decryption happen automatically through database hooks. When a record is saved, sensitive fields are encrypted on the fly. When a record is read, they're decrypted transparently. The rest of the codebase — controllers, API handlers, templates — never needs to know encryption exists.
Failure Handling
The system prioritizes availability over strict enforcement. If encryption fails, the data is written in plaintext and a warning is logged, rather than crashing the application. Similarly, if decryption fails (e.g., due to a missing or incorrect key), the system logs a warning and returns the stored value without modification.
Get George Petropoulos’s stories in your inbox
Join Medium for free to get updates from this writer.
This is a deliberate tradeoff: in an operational phishing simulation, a hard failure at runtime may be more disruptive than temporarily storing a value unencrypted.
Backward Compatibility
One of the core design goals was zero disruption for existing users:
- No encryption key set → Application behaves exactly as before (plaintext everywhere)
- Key set, but data is plaintext → Values are returned as-is (no prefix detected)
- Key set, data is encrypted → Data is decrypted normally
- Already-encrypted value saved again → Detected via prefix and not re-encrypted
- Wrong key for encrypted data → Decryption fails gracefully; application continues running
- Mixed encrypted/plaintext data → Fully supported; each value is handled independently
This means you can enable encryption at any time, migrate gradually, and even reverse the migration if needed. There’s no point of no return.
How to Use Database Encryption
Step 1: Generate an Encryption Key
./gophish --generate-encryption-key# Output:
# ANGLERPHISH_ENCRYPTION_KEY=BaekNXL8PWAcGT4k9MTmKrBAqilaMaTJ4eHET565aHM=
Save this key securely — and be sure not to lose it!
Step 2: Set the Environment Variable
# Linux/Mac
export ANGLERPHISH_ENCRYPTION_KEY=BaekNXL8PWAcGT4k9MTmKrBAqilaMaTJ4eHET565aHM=# Windows CMD
set ANGLERPHISH_ENCRYPTION_KEY=BaekNXL8PWAcGT4k9MTmKrBAqilaMaTJ4eHET565aHM=
# Windows PowerShell
$env:ANGLERPHISH_ENCRYPTION_KEY="BaekNXL8PWAcGT4k9MTmKrBAqilaMaTJ4eHET565aHM="
Step 3: Migrate Existing Data
If you have an existing database with plaintext fields, migrate them to an encrypted state:
./gophish --migrate-encryption# Output:
# Migrating plaintext fields to encrypted...
# SMTP passwords: 5 encrypted, 0 skipped
# SMS provider configs: 3 encrypted, 0 skipped
# IMAP passwords: 2 encrypted, 0 skipped
# Event details (captured data): 12 encrypted, 0 skipped
# Migration complete!
Note 1: Encrypted data can coexist with plaintext. If this step is skipped, only newly created or updated data will be encrypted, while existing data will remain in plaintext.
Note 2: The
eventstable also includes entries such as Email Sent and Campaign Created. These entries do not contain sensitivedetails, which is why they appear as skipped in the screenshot.
Step 4: Run Normally
./gophish# Output includes: "Database encryption is enabled"
Press enter or click to view image in full size
From this point on, all new data is automatically encrypted when saved and decrypted when read. No configuration changes needed beyond the environment variable.
Available Commands
Anglerphish provides a set of CLI commands for managing encryption. Below is a quick reference:
# ============ KEY MANAGEMENT ============
./gophish --generate-encryption-key
# Outputs: ANGLERPHISH_ENCRYPTION_KEY=BaekNXL8P...# ============ MIGRATION ============
./gophish --migrate-encryption # Encrypt all plaintext fields
./gophish --migrate-decryption # Reverse - decrypt back to plaintext
./gophish --migrate-encryption --dry-run # Preview without changes
./gophish --migrate-decryption --dry-run # Preview without changes
# ============ STATUS ============
./gophish --encryption-status
# Shows: Encryption Enabled: Yes/No
# SMTP Passwords: 5 encrypted, 2 plaintext
# SMS Configs: 3 encrypted, 0 plaintext
# IMAP Passwords: 1 encrypted, 1 plaintext
# Event Details: 12 encrypted, 0 plaintext
# ============ NORMAL OPERATION ============
./gophish
# Runs normally - encryption auto-detected from env var
Deployment Checklist
Before deploying encryption in production, follow this indicative checklist:
Enable Encryption
When using environment variables, the encryption key must be set on every restart unless persisted. If Anglerphish starts without the encryption key and finds encrypted values in the database, read operations on those fields will fail gracefully — the application will continue running but sensitive fields will be unreadable until the key is restored.
Example in Linux/Mac:
# 1. BACKUP YOUR DATABASE FIRST!
cp gophish.db gophish.db.backup# 2. Generate a strong encryption key
./gophish --generate-encryption-key
# Save this key securely — DON'T LOSE IT!
# 3. Set the encryption key
export ANGLERPHISH_ENCRYPTION_KEY="your-generated-key"
# 4. Preview what will be encrypted (no changes made)
./gophish --migrate-encryption --dry-run
# 5. Run the actual migration
./gophish --migrate-encryption
# 6. Verify the migration worked
./gophish --encryption-status
Note: Avoid setting sensitive values directly in the shell when possible, as they may be recorded in shell history. Prefer using environment files or secret management systems in production.
Persisting the Key
Store the encryption key in a dedicated environment file with root-only access — this keeps the key out of config files, command history, and source control, while ensuring it persists across reboots.
Linux (systemd service):
# Create a secure secrets file
sudo mkdir -p /etc/anglerphish
echo "ANGLERPHISH_ENCRYPTION_KEY=your-generated-key" | sudo tee /etc/anglerphish/secrets.env
sudo chmod 600 /etc/anglerphish/secrets.env
sudo chown root:root /etc/anglerphish/secrets.env# /etc/systemd/system/anglerphish.service
[Service]
EnvironmentFile=/etc/anglerphish/secrets.env
ExecStart=/opt/anglerphish/gophish⚠️ NEVER LOSE THE ENCRYPTION KEY! ⚠️
If the encryption key is lost, all encrypted data becomes permanently unrecoverable.
Conclusion
Application-layer encryption in Anglerphish closes a gap that GoPhish originally left open: data at rest was only as secure as the database file itself. Adding optional AES-256-GCM changes that. Even if someone gets hold of a backup or a raw database dump, the sensitive data inside it is no longer immediately usable.
That said, this doesn’t solve security on its own.
If an attacker has access to the running server, or your keys are poorly managed, encryption won’t save you. It simply raises the bar for a specific class of attack — offline access to stolen data.
Think of it as containment, not prevention.
Used properly, this layer makes database exposure far less damaging. But it still depends on the basics: locked-down servers, controlled access, and careful handling of encryption keys.