A digital dead drop functions like its physical counterpart—a secure location where information can be left for later pickup without both parties being present simultaneously. For developers and power users seeking secure communication channels, understanding how to set up dead drops provides valuable tools for privacy-conscious information exchange.
This guide covers three approaches to building secure dead drops: GPG-based encrypted file drops, Tor onion service implementations, and custom server configurations.
Understanding Digital Dead Drops
Traditional communication requires simultaneous presence—both parties must be online at the same time. Dead drops break this requirement by separating the upload and download actions. The sender leaves encrypted data at a predetermined location; the recipient retrieves it later using a shared key or authentication mechanism.
Legitimate use cases include secure tip lines, investigative journalism communications, incident response coordination, and any scenario where metadata minimization matters. The key security property is temporal separation: sender and receiver never directly communicate.
Method 1: GPG-Encrypted File Drops
The simplest approach uses GPG encryption with a shared drop location. This method requires minimal infrastructure—a simple file server or even cloud storage.
Setup Process
First, generate a dedicated dead drop key pair:
gpg --full-generate-key
# Select RSA, 4096 bits, no expiration for simplicity
# Use a dedicated email like deaddrop@example.com
Export the public key for drop users:
gpg --armor --export deaddrop@example.com > deaddrop-public.asc
Drop Implementation
Create a script to handle encrypted uploads:
#!/bin/bash
# drop-upload.sh - Upload encrypted content to dead drop
DROP_DIR="/var/www/drops"
RECIPIENT="deaddrop@example.com"
if [ -z "$1" ]; then
echo "Usage: $0 <file-to-drop>"
exit 1
fi
INPUT_FILE="$1"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
OUTPUT_FILE="$DROP_DIR/drop-$TIMESTAMP.gpg"
# Encrypt file for recipient
gpg --encrypt --recipient "$RECIPIENT" --output "$OUTPUT_FILE" "$INPUT_FILE"
echo "Drop created: $OUTPUT_FILE"
echo "Share this filename with the recipient"
Recipients decrypt using:
gpg --decrypt --output recovered-file.txt drop-TIMESTAMP.gpg
Security Considerations
This method provides confidentiality through GPG encryption but offers no anonymity by default. Server logs reveal upload and download patterns. For enhanced anonymity, combine with Tor Browser or a VPN.
Method 2: Tor Onion Service Dead Drops
Onion services provide inherent encryption and can be configured for anonymity. This approach creates a dedicated .onion address that accepts encrypted submissions.
Prerequisites
Install Tor and configure an onion service:
# Install Tor (Debian/Ubuntu)
sudo apt install tor
# Configure onion service
sudo tee /etc/tor/torrc << 'EOF'
HiddenServiceDir /var/lib/tor/deaddrop/
HiddenServicePort 80 127.0.0.1:8080
EOF
sudo systemctl restart tor
Retrieve your onion address:
sudo cat /var/lib/tor/deaddrop/hostname
Submission Handler
Create a simple Python Flask application to receive encrypted submissions:
from flask import Flask, request, render_template_string
import os
from datetime import datetime
app = Flask(__name__)
DROP_DIR = '/var/www/drops'
os.makedirs(DROP_DIR, exist_ok=True)
HTML_FORM = '''
<!DOCTYPE html>
<html>
<head><title>Secure Drop</title></head>
<body>
<h1>Secure Information Drop</h1>
<p>Encrypt your message using the public key before submitting.</p>
<form method="post" enctype="multipart/form-data">
<textarea name="message" rows="10" cols="50" placeholder="Paste encrypted content here"></textarea><br>
<input type="submit" value="Submit Securely">
</form>
</body>
</html>
'''
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
message = request.form.get('message', '')
if message:
timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')
filename = os.path.join(DROP_DIR, f'drop-{timestamp}.txt')
with open(filename, 'w') as f:
f.write(message)
return 'Submission received securely.'
return render_template_string(HTML_FORM)
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8080)
Run behind a local Nginx or Apache proxy, then start the Tor onion service.
Public Key Distribution
Distribute your GPG public key through the onion service:
<!-- Add to your HTML -->
<a href="/deaddrop-public.asc">Public Key</a>
Senders download the key, encrypt their message locally, then paste the encrypted content into the submission form.
Method 3: Custom Implementation with Access Codes
For more control, implement a system with expiring access codes. This adds authentication without requiring GPG.
import hashlib
import secrets
from datetime import datetime, timedelta
class SecureDeadDrop:
def __init__(self, storage_path):
self.storage = storage_path
self.codes = {}
def create_drop(self, content, expiry_hours=24):
"""Create a new drop with expiration"""
drop_id = secrets.token_urlsafe(16)
access_code = secrets.token_hex(8)
# Hash the code for storage (never store plain codes)
code_hash = hashlib.sha256(access_code.encode()).hexdigest()
with open(f"{self.storage}/{drop_id}.dat", 'w') as f:
f.write(content)
self.codes[drop_id] = {
'hash': code_hash,
'expires': datetime.now() + timedelta(hours=expiry_hours)
}
return drop_id, access_code
def retrieve_drop(self, drop_id, access_code):
"""Retrieve drop content if code is valid and not expired"""
if drop_id not in self.codes:
return None
code_data = self.codes[drop_id]
code_hash = hashlib.sha256(access_code.encode()).hexdigest()
if code_hash != code_data['hash']:
return None # Wrong code
if datetime.now() > code_data['expires']:
return None # Expired
try:
with open(f"{self.storage}/{drop_id}.dat", 'r') as f:
return f.read()
except FileNotFoundError:
return None
# Usage
drop = SecureDeadDrop('/var/www/drops')
drop_id, code = drop.create_drop("Sensitive information here")
print(f"Drop ID: {drop_id}, Access Code: {code}")
This implementation provides:
- Access codes that expire after a configurable duration
- No plaintext code storage (only hashes)
- Automatic cleanup of expired drops (implement via cron job)
Security Best Practices
Regardless of implementation choice, follow these principles:
Metadata minimization matters. Tor onion services hide IP addresses but timing patterns may still reveal information. Consider adding random delays before serving content.
Key management requires attention. If using GPG, protect your private key with a strong passphrase. Consider hardware security modules or air-gapped systems for high-value keys.
File deletion should be automatic. Implement cron jobs or background processes to remove drops after retrieval or expiration.
Transport encryption remains essential. Even with onion services, use HTTPS/TLS for any web interface. Let’s Encrypt provides free certificates.
Deployment Considerations
For production deployments, consider additional hardening:
- Run the drop service in a container with limited capabilities
- Use separate authentication for admin functions
- Implement rate limiting to prevent abuse
- Log access attempts for security monitoring while avoiding sensitive data logging
Getting Started
Begin with the GPG-based method if you need quick setup with minimal infrastructure. Move to Tor onion services when anonymity is critical. The custom implementation suits scenarios requiring fine-grained control over drop lifecycle and access patterns.
Each approach trades simplicity for control. The right choice depends on your specific threat model and operational requirements.
For additional privacy tools and security guides, explore the Privacy Tools Guide collection.
Related Articles
- How To Set Up Encrypted Dead Drop Using Onionshare For Sourc
- Set Up a Dead Man’s Switch Email That Sends Credentials If
- Set Up Dead Man’s Switch Using Cron Job to Release Encrypted
- How to Set Up a Canary Trap to Detect Information Leaks
- Lawyer Client Privilege Digital Communication Secure Setup C
Built by theluckystrike — More at zovo.one