Privacy Tools Guide

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:

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:

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.

Built by theluckystrike — More at zovo.one