commit 1e77562ec066379ca2e62af05cfb7034b55cb02a Author: Aaron D. Lee Date: Sat Dec 27 03:14:32 2025 -0500 Initial verion. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..80aac8e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,23 @@ +# Git +.git +.gitignore + +# Python +__pycache__/ +*.py[cod] +venv/ + +# IDE +.vscode/ +.idea/ + +# Uploads +uploads/* + +# Docs +README.md + +# Docker +Dockerfile +docker-compose.yml +.dockerignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2fd8ca0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +venv/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# Uploads (user data) +uploads/* +!uploads/.gitkeep + +# Environment +.env +.env.local + +# OS +.DS_Store +Thumbs.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bf41094 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +FROM python:3.11-slim + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Set work directory +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + libc-dev \ + libffi-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application +COPY . . + +# Create upload directory +RUN mkdir -p /tmp/stego_uploads + +# Create non-root user for security +RUN useradd -m -u 1000 stego && chown -R stego:stego /app /tmp/stego_uploads +USER stego + +# Expose port +EXPOSE 5000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/')" || exit 1 + +# Run with gunicorn in production +CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "--threads", "4", "--timeout", "60", "app:app"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..8cbc439 --- /dev/null +++ b/README.md @@ -0,0 +1,149 @@ +# Stegasoo Web Service + +A containerized Flask + Bootstrap web UI for hybrid Photo + Day-Phrase + PIN steganography. + +![Python](https://img.shields.io/badge/Python-3.11+-blue) +![Flask](https://img.shields.io/badge/Flask-3.0+-green) +![Docker](https://img.shields.io/badge/Docker-Ready-blue) +![Security](https://img.shields.io/badge/Security-AES--256--GCM-red) + +## Features + +- 🔐 **AES-256-GCM** authenticated encryption +- 🧠 **Argon2id** memory-hard key derivation (256MB) +- 🎲 **Pseudo-random pixel selection** defeats steganalysis +- 📅 **Daily key rotation** with 3-word phrases +- 🔢 **Static PIN** for additional entropy +- 🖼️ **Reference photo** as "something you have" +- 🌐 **Web UI** with Bootstrap 5 dark theme + +## Quick Start + +### Docker (Recommended) + +```bash +# Build and run +docker-compose up -d + +# Access at http://localhost:5000 +``` + +### Manual Installation + +```bash +# Create virtual environment +python -m venv venv +source venv/bin/activate # Linux/Mac +# or: venv\Scripts\activate # Windows + +# Install dependencies +pip install -r requirements.txt + +# Run development server +python app.py + +# Or production with gunicorn +gunicorn --bind 0.0.0.0:5000 app:app +``` + +## Usage + +### 1. Generate Credentials + +Visit `/generate` to create: +- **7 three-word phrases** (one per day of week) +- **1 six-digit PIN** (same every day) + +Memorize these! Don't save them. + +### 2. Encode a Message + +Visit `/encode` and provide: +- **Reference photo** - A photo both parties have (NOT transmitted) +- **Carrier image** - The image to hide your message in +- **Message** - Your secret text +- **Day phrase** - Today's 3-word phrase +- **PIN** - Your static 6-digit PIN + +Download the stego image and share it through any channel. + +### 3. Decode a Message + +Visit `/decode` and provide: +- **Reference photo** - Same photo used for encoding +- **Stego image** - The image containing the hidden message +- **Day phrase** - The phrase for the day it was encoded +- **PIN** - Your static PIN + +## Security Model + +| Component | Entropy | Purpose | +|-----------|---------|---------| +| Reference Photo | ~80-256 bits | Something you have | +| 3-Word Phrase | ~33 bits | Something you know (rotates daily) | +| 6-Digit PIN | ~20 bits | Something you know (static) | +| **Combined** | **133+ bits** | **Beyond brute force** | + +### Attack Resistance + +| Attack | Result | +|--------|--------| +| Brute force | 2^133 combinations = impossible | +| Rainbow tables | Random salt per message | +| Steganalysis | Random pixel selection defeats detection | +| GPU cracking | Argon2 requires 256MB RAM per attempt | + +## API Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/` | GET | Home page | +| `/generate` | GET/POST | Generate phrase card + PIN | +| `/encode` | GET/POST | Encode message in image | +| `/decode` | GET/POST | Decode message from image | +| `/about` | GET | Security information | + +## Configuration + +Environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `FLASK_ENV` | production | Flask environment | +| `SECRET_KEY` | random | Session secret (auto-generated) | + +## Production Deployment + +For production, consider: + +1. **HTTPS** - Use nginx reverse proxy with SSL +2. **Rate limiting** - Prevent abuse +3. **Logging** - Monitor for security events +4. **Memory** - Allocate at least 512MB (Argon2 needs 256MB) + +Example nginx config: + +```nginx +server { + listen 443 ssl; + server_name stegocrypt.example.com; + + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + location / { + proxy_pass http://stegocrypt:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + client_max_body_size 50M; + } +} +``` + +## License + +MIT License - Use responsibly. + +## ⚠️ Disclaimer + +This tool is for educational and legitimate privacy purposes only. Users are responsible for complying with applicable laws in their jurisdiction. diff --git a/STEGASOO_WEB_README.md b/STEGASOO_WEB_README.md new file mode 100644 index 0000000..8cbc439 --- /dev/null +++ b/STEGASOO_WEB_README.md @@ -0,0 +1,149 @@ +# Stegasoo Web Service + +A containerized Flask + Bootstrap web UI for hybrid Photo + Day-Phrase + PIN steganography. + +![Python](https://img.shields.io/badge/Python-3.11+-blue) +![Flask](https://img.shields.io/badge/Flask-3.0+-green) +![Docker](https://img.shields.io/badge/Docker-Ready-blue) +![Security](https://img.shields.io/badge/Security-AES--256--GCM-red) + +## Features + +- 🔐 **AES-256-GCM** authenticated encryption +- 🧠 **Argon2id** memory-hard key derivation (256MB) +- 🎲 **Pseudo-random pixel selection** defeats steganalysis +- 📅 **Daily key rotation** with 3-word phrases +- 🔢 **Static PIN** for additional entropy +- 🖼️ **Reference photo** as "something you have" +- 🌐 **Web UI** with Bootstrap 5 dark theme + +## Quick Start + +### Docker (Recommended) + +```bash +# Build and run +docker-compose up -d + +# Access at http://localhost:5000 +``` + +### Manual Installation + +```bash +# Create virtual environment +python -m venv venv +source venv/bin/activate # Linux/Mac +# or: venv\Scripts\activate # Windows + +# Install dependencies +pip install -r requirements.txt + +# Run development server +python app.py + +# Or production with gunicorn +gunicorn --bind 0.0.0.0:5000 app:app +``` + +## Usage + +### 1. Generate Credentials + +Visit `/generate` to create: +- **7 three-word phrases** (one per day of week) +- **1 six-digit PIN** (same every day) + +Memorize these! Don't save them. + +### 2. Encode a Message + +Visit `/encode` and provide: +- **Reference photo** - A photo both parties have (NOT transmitted) +- **Carrier image** - The image to hide your message in +- **Message** - Your secret text +- **Day phrase** - Today's 3-word phrase +- **PIN** - Your static 6-digit PIN + +Download the stego image and share it through any channel. + +### 3. Decode a Message + +Visit `/decode` and provide: +- **Reference photo** - Same photo used for encoding +- **Stego image** - The image containing the hidden message +- **Day phrase** - The phrase for the day it was encoded +- **PIN** - Your static PIN + +## Security Model + +| Component | Entropy | Purpose | +|-----------|---------|---------| +| Reference Photo | ~80-256 bits | Something you have | +| 3-Word Phrase | ~33 bits | Something you know (rotates daily) | +| 6-Digit PIN | ~20 bits | Something you know (static) | +| **Combined** | **133+ bits** | **Beyond brute force** | + +### Attack Resistance + +| Attack | Result | +|--------|--------| +| Brute force | 2^133 combinations = impossible | +| Rainbow tables | Random salt per message | +| Steganalysis | Random pixel selection defeats detection | +| GPU cracking | Argon2 requires 256MB RAM per attempt | + +## API Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/` | GET | Home page | +| `/generate` | GET/POST | Generate phrase card + PIN | +| `/encode` | GET/POST | Encode message in image | +| `/decode` | GET/POST | Decode message from image | +| `/about` | GET | Security information | + +## Configuration + +Environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `FLASK_ENV` | production | Flask environment | +| `SECRET_KEY` | random | Session secret (auto-generated) | + +## Production Deployment + +For production, consider: + +1. **HTTPS** - Use nginx reverse proxy with SSL +2. **Rate limiting** - Prevent abuse +3. **Logging** - Monitor for security events +4. **Memory** - Allocate at least 512MB (Argon2 needs 256MB) + +Example nginx config: + +```nginx +server { + listen 443 ssl; + server_name stegocrypt.example.com; + + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + location / { + proxy_pass http://stegocrypt:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + client_max_body_size 50M; + } +} +``` + +## License + +MIT License - Use responsibly. + +## ⚠️ Disclaimer + +This tool is for educational and legitimate privacy purposes only. Users are responsible for complying with applicable laws in their jurisdiction. diff --git a/app.py b/app.py new file mode 100644 index 0000000..e5d7a82 --- /dev/null +++ b/app.py @@ -0,0 +1,602 @@ +#!/usr/bin/env python3 +""" +Steganography Web Service v3.1 +Flask + Bootstrap web UI for the hybrid Photo + Day-Phrase + PIN system +""" + +import os +import io +import secrets +import hashlib +import struct +from datetime import datetime +from flask import Flask, render_template, request, send_file, jsonify, flash, redirect, url_for +from werkzeug.utils import secure_filename +from PIL import Image +from secureDeleter import SecureDeleter + +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.backends import default_backend + +try: + from argon2.low_level import hash_secret_raw, Type + HAS_ARGON2 = True +except ImportError: + HAS_ARGON2 = False + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + from cryptography.hazmat.primitives import hashes + +# ============================================================================ +# FLASK APP CONFIGURATION +# ============================================================================ + +app = Flask(__name__) +app.secret_key = secrets.token_hex(32) +app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # 5MB max upload per file +app.config['UPLOAD_FOLDER'] = '/tmp/stego_uploads' + +# Limits +MAX_IMAGE_PIXELS = 4000000 # 4 megapixels max (e.g., 2000x2000) +MAX_MESSAGE_SIZE = 50000 # 50KB message max + +os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) + +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'bmp', 'gif'} + +# ============================================================================ +# CRYPTO CONFIGURATION +# ============================================================================ + +MAGIC_HEADER = b'\x89ST3' +VERSION = 3 +SALT_SIZE = 32 +IV_SIZE = 12 +TAG_SIZE = 16 +ARGON2_TIME_COST = 4 +ARGON2_MEMORY_COST = 256 * 1024 +ARGON2_PARALLELISM = 4 + +DAY_NAMES = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + +# BIP-39 wordlist (loaded from file) +BIP39_FILE = os.path.join(os.path.dirname(__file__), 'bip39-words.txt') +with open(BIP39_FILE, 'r') as f: + BIP39_WORDS = [line.strip() for line in f if line.strip()] + +# ============================================================================ +# SECURE CLEANUP +# ============================================================================ + +def secure_cleanup_uploads(): + """Securely delete any stray files in uploads directory.""" + upload_dir = app.config['UPLOAD_FOLDER'] + if os.path.exists(upload_dir): + for filename in os.listdir(upload_dir): + filepath = os.path.join(upload_dir, filename) + if os.path.isfile(filepath) and not filename.startswith('.'): + try: + deleter = SecureDeleter(filepath) + deleter.execute() + except Exception as e: + # Fallback to regular delete + os.remove(filepath) + +# ============================================================================ +# HELPER FUNCTIONS +# ============================================================================ + +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + + +def generate_pin(length=6): + """Generate a random PIN of specified length (6-8 digits).""" + return ''.join(str(secrets.randbelow(10)) for _ in range(length)) + + +def generate_day_phrases(words_per_phrase=3): + phrases = {} + for day in DAY_NAMES: + words = [secrets.choice(BIP39_WORDS) for _ in range(words_per_phrase)] + phrases[day] = ' '.join(words) + return phrases + + +def hash_photo(image_data): + """Compute deterministic hash of photo content.""" + img = Image.open(io.BytesIO(image_data)) + img = img.convert('RGB') + pixels = img.tobytes() + h = hashlib.sha256(pixels).digest() + h = hashlib.sha256(h + pixels[:1024]).digest() + return h + + +def derive_hybrid_key(photo_data, day_phrase, date_str, salt, pin=""): + """Derive encryption key from photo + phrase + PIN + date + salt.""" + photo_hash = hash_photo(photo_data) + + key_material = ( + photo_hash + + day_phrase.lower().encode() + + pin.encode() + + date_str.encode() + + salt + ) + + if HAS_ARGON2: + key = hash_secret_raw( + secret=key_material, + salt=salt[:32], + time_cost=ARGON2_TIME_COST, + memory_cost=ARGON2_MEMORY_COST, + parallelism=ARGON2_PARALLELISM, + hash_len=32, + type=Type.ID + ) + else: + kdf = PBKDF2HMAC( + algorithm=hashes.SHA512(), + length=32, + salt=salt, + iterations=600000, + backend=default_backend() + ) + key = kdf.derive(key_material) + + return key + + +def derive_pixel_key(photo_data, day_phrase, date_str, pin=""): + """Derive key for pixel selection.""" + photo_hash = hash_photo(photo_data) + material = photo_hash + day_phrase.lower().encode() + pin.encode() + date_str.encode() + return hashlib.sha256(material + b"pixel_selection").digest() + + +def encrypt_message(message, photo_data, day_phrase, date_str, pin=""): + """Encrypt message using hybrid key derivation.""" + salt = secrets.token_bytes(SALT_SIZE) + key = derive_hybrid_key(photo_data, day_phrase, date_str, salt, pin) + iv = secrets.token_bytes(IV_SIZE) + + # Random padding + if isinstance(message, str): + message = message.encode() + + padding_len = secrets.randbelow(256) + 64 + padded_len = ((len(message) + padding_len + 255) // 256) * 256 + padding_needed = padded_len - len(message) + padding = secrets.token_bytes(padding_needed - 4) + struct.pack('>I', len(message)) + padded_message = message + padding + + cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=default_backend()) + encryptor = cipher.encryptor() + encryptor.authenticate_additional_data(MAGIC_HEADER + bytes([VERSION])) + ciphertext = encryptor.update(padded_message) + encryptor.finalize() + + date_bytes = date_str.encode() + + return ( + MAGIC_HEADER + + bytes([VERSION]) + + bytes([len(date_bytes)]) + + date_bytes + + salt + + iv + + encryptor.tag + + ciphertext + ) + + +def generate_pixel_indices(key, num_pixels, num_needed): + """ + Generate pseudo-random pixel indices. + + Optimized: Instead of shuffling ALL pixels (slow for large images), + we generate indices directly using PRNG and handle collisions. + """ + if num_needed >= num_pixels // 2: + # If we need many pixels, fall back to shuffle (rare case) + nonce = b'\x00' * 16 + cipher = Cipher(algorithms.ChaCha20(key, nonce), mode=None, backend=default_backend()) + encryptor = cipher.encryptor() + + indices = list(range(num_pixels)) + random_bytes = encryptor.update(b'\x00' * (num_pixels * 4)) + + for i in range(num_pixels - 1, 0, -1): + j_bytes = random_bytes[(num_pixels - 1 - i) * 4:(num_pixels - i) * 4] + j = int.from_bytes(j_bytes, 'big') % (i + 1) + indices[i], indices[j] = indices[j], indices[i] + + return indices[:num_needed] + + # Optimized path: generate indices directly + # Use key to seed selection, ensuring deterministic results + selected = [] + used = set() + + # Generate random bytes for index selection + nonce = b'\x00' * 16 + cipher = Cipher(algorithms.ChaCha20(key, nonce), mode=None, backend=default_backend()) + encryptor = cipher.encryptor() + + # Generate more than needed to handle collisions + bytes_needed = (num_needed * 2) * 4 # 4 bytes per index, 2x for collisions + random_bytes = encryptor.update(b'\x00' * bytes_needed) + + byte_offset = 0 + while len(selected) < num_needed and byte_offset < len(random_bytes) - 4: + idx = int.from_bytes(random_bytes[byte_offset:byte_offset + 4], 'big') % num_pixels + byte_offset += 4 + + if idx not in used: + used.add(idx) + selected.append(idx) + + # If we still need more (very unlikely), generate additional + while len(selected) < num_needed: + extra_bytes = encryptor.update(b'\x00' * 4) + idx = int.from_bytes(extra_bytes, 'big') % num_pixels + if idx not in used: + used.add(idx) + selected.append(idx) + + return selected + + +def embed_in_image(carrier_data, encrypted_data, pixel_key, bits_per_channel=1): + """Embed encrypted data in carrier image. Returns PNG bytes.""" + img = Image.open(io.BytesIO(carrier_data)) + if img.mode != 'RGB': + img = img.convert('RGB') + + pixels = list(img.getdata()) + num_pixels = len(pixels) + + bits_per_pixel = 3 * bits_per_channel + max_bytes = (num_pixels * bits_per_pixel) // 8 + + data_with_len = struct.pack('>I', len(encrypted_data)) + encrypted_data + + if len(data_with_len) > max_bytes: + raise ValueError(f"Carrier too small. Need {len(data_with_len)} bytes, have {max_bytes}") + + binary_data = ''.join(format(b, '08b') for b in data_with_len) + pixels_needed = (len(binary_data) + bits_per_pixel - 1) // bits_per_pixel + + selected_indices = generate_pixel_indices(pixel_key, num_pixels, pixels_needed) + + new_pixels = list(pixels) + clear_mask = 0xFF ^ ((1 << bits_per_channel) - 1) + + bit_idx = 0 + for pixel_idx in selected_indices: + if bit_idx >= len(binary_data): + break + + r, g, b = new_pixels[pixel_idx] + + for channel_idx, channel_val in enumerate([r, g, b]): + if bit_idx >= len(binary_data): + break + bits = binary_data[bit_idx:bit_idx + bits_per_channel].ljust(bits_per_channel, '0') + new_val = (channel_val & clear_mask) | int(bits, 2) + + if channel_idx == 0: + r = new_val + elif channel_idx == 1: + g = new_val + else: + b = new_val + + bit_idx += bits_per_channel + + new_pixels[pixel_idx] = (r, g, b) + + stego_img = Image.new('RGB', img.size) + stego_img.putdata(new_pixels) + + output = io.BytesIO() + stego_img.save(output, 'PNG') + output.seek(0) + + return output.getvalue(), { + 'pixels_modified': len(selected_indices), + 'total_pixels': num_pixels, + 'capacity_used': len(data_with_len) / max_bytes + } + + +def extract_from_image(image_data, pixel_key, bits_per_channel=1): + """Extract hidden data from image.""" + img = Image.open(io.BytesIO(image_data)) + if img.mode != 'RGB': + img = img.convert('RGB') + + pixels = list(img.getdata()) + num_pixels = len(pixels) + bits_per_pixel = 3 * bits_per_channel + + # First extract enough to get length + initial_pixels = (32 + bits_per_pixel - 1) // bits_per_pixel + 10 + initial_indices = generate_pixel_indices(pixel_key, num_pixels, initial_pixels) + + binary_data = '' + for pixel_idx in initial_indices: + r, g, b = pixels[pixel_idx] + for channel in [r, g, b]: + for bit_pos in range(bits_per_channel - 1, -1, -1): + binary_data += str((channel >> bit_pos) & 1) + + try: + length_bits = binary_data[:32] + data_length = struct.unpack('>I', int(length_bits, 2).to_bytes(4, 'big'))[0] + except: + return None + + max_possible = (num_pixels * bits_per_pixel) // 8 - 4 + if data_length > max_possible or data_length < 10: + return None + + total_bits = (4 + data_length) * 8 + pixels_needed = (total_bits + bits_per_pixel - 1) // bits_per_pixel + + selected_indices = generate_pixel_indices(pixel_key, num_pixels, pixels_needed) + + binary_data = '' + for pixel_idx in selected_indices: + r, g, b = pixels[pixel_idx] + for channel in [r, g, b]: + for bit_pos in range(bits_per_channel - 1, -1, -1): + binary_data += str((channel >> bit_pos) & 1) + + data_bits = binary_data[32:32 + (data_length * 8)] + + data_bytes = bytearray() + for i in range(0, len(data_bits), 8): + byte_bits = data_bits[i:i+8] + if len(byte_bits) == 8: + data_bytes.append(int(byte_bits, 2)) + + return bytes(data_bytes) + + +def parse_header(encrypted_data): + """Parse v3 header.""" + if len(encrypted_data) < 10 or encrypted_data[:4] != MAGIC_HEADER: + return None + + date_len = encrypted_data[5] + date_str = encrypted_data[6:6+date_len].decode() + + offset = 6 + date_len + salt = encrypted_data[offset:offset+SALT_SIZE] + offset += SALT_SIZE + iv = encrypted_data[offset:offset+IV_SIZE] + offset += IV_SIZE + tag = encrypted_data[offset:offset+TAG_SIZE] + offset += TAG_SIZE + ciphertext = encrypted_data[offset:] + + return {'date': date_str, 'salt': salt, 'iv': iv, 'tag': tag, 'ciphertext': ciphertext} + + +def decrypt_message(encrypted_data, photo_data, day_phrase, pin=""): + """Decrypt message.""" + header = parse_header(encrypted_data) + if not header: + return None + + key = derive_hybrid_key(photo_data, day_phrase, header['date'], header['salt'], pin) + + cipher = Cipher( + algorithms.AES(key), + modes.GCM(header['iv'], header['tag']), + backend=default_backend() + ) + decryptor = cipher.decryptor() + decryptor.authenticate_additional_data(MAGIC_HEADER + bytes([VERSION])) + + try: + padded_plaintext = decryptor.update(header['ciphertext']) + decryptor.finalize() + original_length = struct.unpack('>I', padded_plaintext[-4:])[0] + return padded_plaintext[:original_length].decode('utf-8') + except: + return None + + +# ============================================================================ +# FLASK ROUTES +# ============================================================================ + +@app.route('/') +def index(): + return render_template('index.html') + + +@app.route('/generate', methods=['GET', 'POST']) +def generate(): + if request.method == 'POST': + words_per_phrase = int(request.form.get('words_per_phrase', 3)) + pin_length = int(request.form.get('pin_length', 6)) + + # Clamp values to valid ranges + words_per_phrase = max(3, min(12, words_per_phrase)) + pin_length = max(6, min(8, pin_length)) + + phrases = generate_day_phrases(words_per_phrase) + pin = generate_pin(pin_length) + + # Calculate entropy + phrase_entropy = words_per_phrase * 11 # ~11 bits per BIP-39 word + pin_entropy = int(pin_length * 3.32) # log2(10) ≈ 3.32 bits per digit + total_entropy = phrase_entropy + pin_entropy + + return render_template('generate.html', + phrases=phrases, + pin=pin, + days=DAY_NAMES, + generated=True, + words_per_phrase=words_per_phrase, + pin_length=pin_length, + phrase_entropy=phrase_entropy, + pin_entropy=pin_entropy, + total_entropy=total_entropy) + return render_template('generate.html', generated=False) + + +@app.route('/encode', methods=['GET', 'POST']) +def encode(): + + # Get day of week + day_of_week = datetime.now().strftime("%A") + + if request.method == 'POST': + try: + + + # Get files + ref_photo = request.files.get('reference_photo') + carrier = request.files.get('carrier') + + if not ref_photo or not carrier: + flash('Both reference photo and carrier image are required', 'error') + return render_template('encode.html') + + if not allowed_file(ref_photo.filename) or not allowed_file(carrier.filename): + flash('Invalid file type. Use PNG, JPG, or BMP', 'error') + return render_template('encode.html') + + # Get form data + message = request.form.get('message', '') + day_phrase = request.form.get('day_phrase', '') + pin = request.form.get('pin', '') + + if not message or not day_phrase: + flash('Message and day phrase are required', 'error') + return render_template('encode.html') + + # Check message size + if len(message) > MAX_MESSAGE_SIZE: + flash(f'Message too long. Max {MAX_MESSAGE_SIZE // 1000}KB allowed.', 'error') + return render_template('encode.html') + + # Read files + ref_data = ref_photo.read() + carrier_data = carrier.read() + + # Validate carrier image dimensions + try: + carrier_img = Image.open(io.BytesIO(carrier_data)) + width, height = carrier_img.size + num_pixels = width * height + + if num_pixels > MAX_IMAGE_PIXELS: + max_dim = int(MAX_IMAGE_PIXELS ** 0.5) + flash(f'Carrier image too large ({width}x{height} = {num_pixels:,} pixels). ' + f'Max ~{MAX_IMAGE_PIXELS:,} pixels ({max_dim}x{max_dim}). ' + f'Please resize your image.', 'error') + return render_template('encode.html') + except Exception as e: + flash(f'Could not read carrier image: {str(e)}', 'error') + return render_template('encode.html') + + # Get date + date_str = datetime.now().strftime('%Y-%m-%d') + + # Encrypt + encrypted = encrypt_message(message, ref_data, day_phrase, date_str, pin) + + # Get pixel key + pixel_key = derive_pixel_key(ref_data, day_phrase, date_str, pin) + + # Embed + stego_data, stats = embed_in_image(carrier_data, encrypted, pixel_key) + + # Return as download + return send_file( + io.BytesIO(stego_data), + mimetype='image/png', + as_attachment=True, + #download_name='stego_image.png' + download_name=f'{secrets.token_hex(4)}_{datetime.now().strftime("%Y%m%d")}.png' + ) + + except Exception as e: + flash(f'Error: {str(e)}', 'error') + return render_template('encode.html', day_of_week=day_of_week) + + return render_template('encode.html', day_of_week=day_of_week) + + +@app.route('/decode', methods=['GET', 'POST']) +def decode(): + if request.method == 'POST': + try: + # Get files + ref_photo = request.files.get('reference_photo') + stego_image = request.files.get('stego_image') + + if not ref_photo or not stego_image: + flash('Both reference photo and stego image are required', 'error') + return render_template('decode.html') + + # Get form data + day_phrase = request.form.get('day_phrase', '') + pin = request.form.get('pin', '') + + if not day_phrase: + flash('Day phrase is required', 'error') + return render_template('decode.html') + + # Read files + ref_data = ref_photo.read() + stego_data = stego_image.read() + + # Try to extract and decrypt + # We need to try with today's date first for pixel key + date_str = datetime.now().strftime('%Y-%m-%d') + pixel_key = derive_pixel_key(ref_data, day_phrase, date_str, pin) + + encrypted = extract_from_image(stego_data, pixel_key) + + if encrypted: + # Parse to get actual date + header = parse_header(encrypted) + if header and header['date'] != date_str: + # Re-extract with correct date + pixel_key = derive_pixel_key(ref_data, day_phrase, header['date'], pin) + encrypted = extract_from_image(stego_data, pixel_key) + + if not encrypted: + flash('Could not extract data. Check your inputs.', 'error') + return render_template('decode.html') + + message = decrypt_message(encrypted, ref_data, day_phrase, pin) + + if message: + return render_template('decode.html', decoded_message=message) + else: + flash('Decryption failed. Wrong phrase, PIN, or reference photo.', 'error') + return render_template('decode.html') + + except Exception as e: + flash(f'Error: {str(e)}', 'error') + return render_template('decode.html') + + return render_template('decode.html') + + +@app.route('/about') +def about(): + return render_template('about.html', has_argon2=HAS_ARGON2) + + +# ============================================================================ +# MAIN +# ============================================================================ + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=False) diff --git a/bip39-words.txt b/bip39-words.txt new file mode 100644 index 0000000..942040e --- /dev/null +++ b/bip39-words.txt @@ -0,0 +1,2048 @@ +abandon +ability +able +about +above +absent +absorb +abstract +absurd +abuse +access +accident +account +accuse +achieve +acid +acoustic +acquire +across +act +action +actor +actress +actual +adapt +add +addict +address +adjust +admit +adult +advance +advice +aerobic +affair +afford +afraid +again +age +agent +agree +ahead +aim +air +airport +aisle +alarm +album +alcohol +alert +alien +all +alley +allow +almost +alone +alpha +already +also +alter +always +amateur +amazing +among +amount +amused +analyst +anchor +ancient +anger +angle +angry +animal +ankle +announce +annual +another +answer +antenna +antique +anxiety +any +apart +apology +appear +apple +approve +april +arch +arctic +area +arena +argue +arm +armed +armor +army +around +arrange +arrest +arrive +arrow +art +artefact +artist +artwork +ask +aspect +assault +asset +assist +assume +asthma +athlete +atom +attack +attend +attitude +attract +auction +audit +august +aunt +author +auto +autumn +average +avocado +avoid +awake +aware +away +awesome +awful +awkward +axis +baby +bachelor +bacon +badge +bag +balance +balcony +ball +bamboo +banana +banner +bar +barely +bargain +barrel +base +basic +basket +battle +beach +bean +beauty +because +become +beef +before +begin +behave +behind +believe +below +belt +bench +benefit +best +betray +better +between +beyond +bicycle +bid +bike +bind +biology +bird +birth +bitter +black +blade +blame +blanket +blast +bleak +bless +blind +blood +blossom +blouse +blue +blur +blush +board +boat +body +boil +bomb +bone +bonus +book +boost +border +boring +borrow +boss +bottom +bounce +box +boy +bracket +brain +brand +brass +brave +bread +breeze +brick +bridge +brief +bright +bring +brisk +broccoli +broken +bronze +broom +brother +brown +brush +bubble +buddy +budget +buffalo +build +bulb +bulk +bullet +bundle +bunker +burden +burger +burst +bus +business +busy +butter +buyer +buzz +cabbage +cabin +cable +cactus +cage +cake +call +calm +camera +camp +can +canal +cancel +candy +cannon +canoe +canvas +canyon +capable +capital +captain +car +carbon +card +cargo +carpet +carry +cart +case +cash +casino +castle +casual +cat +catalog +catch +category +cattle +caught +cause +caution +cave +ceiling +celery +cement +census +century +cereal +certain +chair +chalk +champion +change +chaos +chapter +charge +chase +chat +cheap +check +cheese +chef +cherry +chest +chicken +chief +child +chimney +choice +choose +chronic +chuckle +chunk +churn +cigar +cinnamon +circle +citizen +city +civil +claim +clap +clarify +claw +clay +clean +clerk +clever +click +client +cliff +climb +clinic +clip +clock +clog +close +cloth +cloud +clown +club +clump +cluster +clutch +coach +coast +coconut +code +coffee +coil +coin +collect +color +column +combine +come +comfort +comic +common +company +concert +conduct +confirm +congress +connect +consider +control +convince +cook +cool +copper +copy +coral +core +corn +correct +cost +cotton +couch +country +couple +course +cousin +cover +coyote +crack +cradle +craft +cram +crane +crash +crater +crawl +crazy +cream +credit +creek +crew +cricket +crime +crisp +critic +crop +cross +crouch +crowd +crucial +cruel +cruise +crumble +crunch +crush +cry +crystal +cube +culture +cup +cupboard +curious +current +curtain +curve +cushion +custom +cute +cycle +dad +damage +damp +dance +danger +daring +dash +daughter +dawn +day +deal +debate +debris +decade +december +decide +decline +decorate +decrease +deer +defense +define +defy +degree +delay +deliver +demand +demise +denial +dentist +deny +depart +depend +deposit +depth +deputy +derive +describe +desert +design +desk +despair +destroy +detail +detect +develop +device +devote +diagram +dial +diamond +diary +dice +diesel +diet +differ +digital +dignity +dilemma +dinner +dinosaur +direct +dirt +disagree +discover +disease +dish +dismiss +disorder +display +distance +divert +divide +divorce +dizzy +doctor +document +dog +doll +dolphin +domain +donate +donkey +donor +door +dose +double +dove +draft +dragon +drama +drastic +draw +dream +dress +drift +drill +drink +drip +drive +drop +drum +dry +duck +dumb +dune +during +dust +dutch +duty +dwarf +dynamic +eager +eagle +early +earn +earth +easily +east +easy +echo +ecology +economy +edge +edit +educate +effort +egg +eight +either +elbow +elder +electric +elegant +element +elephant +elevator +elite +else +embark +embody +embrace +emerge +emotion +employ +empower +empty +enable +enact +end +endless +endorse +enemy +energy +enforce +engage +engine +enhance +enjoy +enlist +enough +enrich +enroll +ensure +enter +entire +entry +envelope +episode +equal +equip +era +erase +erode +erosion +error +erupt +escape +essay +essence +estate +eternal +ethics +evidence +evil +evoke +evolve +exact +example +excess +exchange +excite +exclude +excuse +execute +exercise +exhaust +exhibit +exile +exist +exit +exotic +expand +expect +expire +explain +expose +express +extend +extra +eye +eyebrow +fabric +face +faculty +fade +faint +faith +fall +false +fame +family +famous +fan +fancy +fantasy +farm +fashion +fat +fatal +father +fatigue +fault +favorite +feature +february +federal +fee +feed +feel +female +fence +festival +fetch +fever +few +fiber +fiction +field +figure +file +film +filter +final +find +fine +finger +finish +fire +firm +first +fiscal +fish +fit +fitness +fix +flag +flame +flash +flat +flavor +flee +flight +flip +float +flock +floor +flower +fluid +flush +fly +foam +focus +fog +foil +fold +follow +food +foot +force +forest +forget +fork +fortune +forum +forward +fossil +foster +found +fox +fragile +frame +frequent +fresh +friend +fringe +frog +front +frost +frown +frozen +fruit +fuel +fun +funny +furnace +fury +future +gadget +gain +galaxy +gallery +game +gap +garage +garbage +garden +garlic +garment +gas +gasp +gate +gather +gauge +gaze +general +genius +genre +gentle +genuine +gesture +ghost +giant +gift +giggle +ginger +giraffe +girl +give +glad +glance +glare +glass +glide +glimpse +globe +gloom +glory +glove +glow +glue +goat +goddess +gold +good +goose +gorilla +gospel +gossip +govern +gown +grab +grace +grain +grant +grape +grass +gravity +great +green +grid +grief +grit +grocery +group +grow +grunt +guard +guess +guide +guilt +guitar +gun +gym +habit +hair +half +hammer +hamster +hand +happy +harbor +hard +harsh +harvest +hat +have +hawk +hazard +head +health +heart +heavy +hedgehog +height +hello +helmet +help +hen +hero +hidden +high +hill +hint +hip +hire +history +hobby +hockey +hold +hole +holiday +hollow +home +honey +hood +hope +horn +horror +horse +hospital +host +hotel +hour +hover +hub +huge +human +humble +humor +hundred +hungry +hunt +hurdle +hurry +hurt +husband +hybrid +ice +icon +idea +identify +idle +ignore +ill +illegal +illness +image +imitate +immense +immune +impact +impose +improve +impulse +inch +include +income +increase +index +indicate +indoor +industry +infant +inflict +inform +inhale +inherit +initial +inject +injury +inmate +inner +innocent +input +inquiry +insane +insect +inside +inspire +install +intact +interest +into +invest +invite +involve +iron +island +isolate +issue +item +ivory +jacket +jaguar +jar +jazz +jealous +jeans +jelly +jewel +job +join +joke +journey +joy +judge +juice +jump +jungle +junior +junk +just +kangaroo +keen +keep +ketchup +key +kick +kid +kidney +kind +kingdom +kiss +kit +kitchen +kite +kitten +kiwi +knee +knife +knock +know +lab +label +labor +ladder +lady +lake +lamp +language +laptop +large +later +latin +laugh +laundry +lava +law +lawn +lawsuit +layer +lazy +leader +leaf +learn +leave +lecture +left +leg +legal +legend +leisure +lemon +lend +length +lens +leopard +lesson +letter +level +liar +liberty +library +license +life +lift +light +like +limb +limit +link +lion +liquid +list +little +live +lizard +load +loan +lobster +local +lock +logic +lonely +long +loop +lottery +loud +lounge +love +loyal +lucky +luggage +lumber +lunar +lunch +luxury +lyrics +machine +mad +magic +magnet +maid +mail +main +major +make +mammal +man +manage +mandate +mango +mansion +manual +maple +marble +march +margin +marine +market +marriage +mask +mass +master +match +material +math +matrix +matter +maximum +maze +meadow +mean +measure +meat +mechanic +medal +media +melody +melt +member +memory +mention +menu +mercy +merge +merit +merry +mesh +message +metal +method +middle +midnight +milk +million +mimic +mind +minimum +minor +minute +miracle +mirror +misery +miss +mistake +mix +mixed +mixture +mobile +model +modify +mom +moment +monitor +monkey +monster +month +moon +moral +more +morning +mosquito +mother +motion +motor +mountain +mouse +move +movie +much +muffin +mule +multiply +muscle +museum +mushroom +music +must +mutual +myself +mystery +myth +naive +name +napkin +narrow +nasty +nation +nature +near +neck +need +negative +neglect +neither +nephew +nerve +nest +net +network +neutral +never +news +next +nice +night +noble +noise +nominee +noodle +normal +north +nose +notable +note +nothing +notice +novel +now +nuclear +number +nurse +nut +oak +obey +object +oblige +obscure +observe +obtain +obvious +occur +ocean +october +odor +off +offer +office +often +oil +okay +old +olive +olympic +omit +once +one +onion +online +only +open +opera +opinion +oppose +option +orange +orbit +orchard +order +ordinary +organ +orient +original +orphan +ostrich +other +outdoor +outer +output +outside +oval +oven +over +own +owner +oxygen +oyster +ozone +pact +paddle +page +pair +palace +palm +panda +panel +panic +panther +paper +parade +parent +park +parrot +party +pass +patch +path +patient +patrol +pattern +pause +pave +payment +peace +peanut +pear +peasant +pelican +pen +penalty +pencil +people +pepper +perfect +permit +person +pet +phone +photo +phrase +physical +piano +picnic +picture +piece +pig +pigeon +pill +pilot +pink +pioneer +pipe +pistol +pitch +pizza +place +planet +plastic +plate +play +please +pledge +pluck +plug +plunge +poem +poet +point +polar +pole +police +pond +pony +pool +popular +portion +position +possible +post +potato +pottery +poverty +powder +power +practice +praise +predict +prefer +prepare +present +pretty +prevent +price +pride +primary +print +priority +prison +private +prize +problem +process +produce +profit +program +project +promote +proof +property +prosper +protect +proud +provide +public +pudding +pull +pulp +pulse +pumpkin +punch +pupil +puppy +purchase +purity +purpose +purse +push +put +puzzle +pyramid +quality +quantum +quarter +question +quick +quit +quiz +quote +rabbit +raccoon +race +rack +radar +radio +rail +rain +raise +rally +ramp +ranch +random +range +rapid +rare +rate +rather +raven +raw +razor +ready +real +reason +rebel +rebuild +recall +receive +recipe +record +recycle +reduce +reflect +reform +refuse +region +regret +regular +reject +relax +release +relief +rely +remain +remember +remind +remove +render +renew +rent +reopen +repair +repeat +replace +report +require +rescue +resemble +resist +resource +response +result +retire +retreat +return +reunion +reveal +review +reward +rhythm +rib +ribbon +rice +rich +ride +ridge +rifle +right +rigid +ring +riot +ripple +risk +ritual +rival +river +road +roast +robot +robust +rocket +romance +roof +rookie +room +rose +rotate +rough +round +route +royal +rubber +rude +rug +rule +run +runway +rural +sad +saddle +sadness +safe +sail +salad +salmon +salon +salt +salute +same +sample +sand +satisfy +satoshi +sauce +sausage +save +say +scale +scan +scare +scatter +scene +scheme +school +science +scissors +scorpion +scout +scrap +screen +script +scrub +sea +search +season +seat +second +secret +section +security +seed +seek +segment +select +sell +seminar +senior +sense +sentence +series +service +session +settle +setup +seven +shadow +shaft +shallow +share +shed +shell +sheriff +shield +shift +shine +ship +shiver +shock +shoe +shoot +shop +short +shoulder +shove +shrimp +shrug +shuffle +shy +sibling +sick +side +siege +sight +sign +silent +silk +silly +silver +similar +simple +since +sing +siren +sister +situate +six +size +skate +sketch +ski +skill +skin +skirt +skull +slab +slam +sleep +slender +slice +slide +slight +slim +slogan +slot +slow +slush +small +smart +smile +smoke +smooth +snack +snake +snap +sniff +snow +soap +soccer +social +sock +soda +soft +solar +soldier +solid +solution +solve +someone +song +soon +sorry +sort +soul +sound +soup +source +south +space +spare +spatial +spawn +speak +special +speed +spell +spend +sphere +spice +spider +spike +spin +spirit +split +spoil +sponsor +spoon +sport +spot +spray +spread +spring +spy +square +squeeze +squirrel +stable +stadium +staff +stage +stairs +stamp +stand +start +state +stay +steak +steel +stem +step +stereo +stick +still +sting +stock +stomach +stone +stool +story +stove +strategy +street +strike +strong +struggle +student +stuff +stumble +style +subject +submit +subway +success +such +sudden +suffer +sugar +suggest +suit +summer +sun +sunny +sunset +super +supply +supreme +sure +surface +surge +surprise +surround +survey +suspect +sustain +swallow +swamp +swap +swarm +swear +sweet +swift +swim +swing +switch +sword +symbol +symptom +syrup +system +table +tackle +tag +tail +talent +talk +tank +tape +target +task +taste +tattoo +taxi +teach +team +tell +ten +tenant +tennis +tent +term +test +text +thank +that +theme +then +theory +there +they +thing +this +thought +three +thrive +throw +thumb +thunder +ticket +tide +tiger +tilt +timber +time +tiny +tip +tired +tissue +title +toast +tobacco +today +toddler +toe +together +toilet +token +tomato +tomorrow +tone +tongue +tonight +tool +tooth +top +topic +topple +torch +tornado +tortoise +toss +total +tourist +toward +tower +town +toy +track +trade +traffic +tragic +train +transfer +trap +trash +travel +tray +treat +tree +trend +trial +tribe +trick +trigger +trim +trip +trophy +trouble +truck +true +truly +trumpet +trust +truth +try +tube +tuition +tumble +tuna +tunnel +turkey +turn +turtle +twelve +twenty +twice +twin +twist +two +type +typical +ugly +umbrella +unable +unaware +uncle +uncover +under +undo +unfair +unfold +unhappy +uniform +unique +unit +universe +unknown +unlock +until +unusual +unveil +update +upgrade +uphold +upon +upper +upset +urban +urge +usage +use +used +useful +useless +usual +utility +vacant +vacuum +vague +valid +valley +valve +van +vanish +vapor +various +vast +vault +vehicle +velvet +vendor +venture +venue +verb +verify +version +very +vessel +veteran +viable +vibrant +vicious +victory +video +view +village +vintage +violin +virtual +virus +visa +visit +visual +vital +vivid +vocal +voice +void +volcano +volume +vote +voyage +wage +wagon +wait +walk +wall +walnut +want +warfare +warm +warrior +wash +wasp +waste +water +wave +way +wealth +weapon +wear +weasel +weather +web +wedding +weekend +weird +welcome +west +wet +whale +what +wheat +wheel +when +where +whip +whisper +wide +width +wife +wild +will +win +window +wine +wing +wink +winner +winter +wire +wisdom +wise +wish +witness +wolf +woman +wonder +wood +wool +word +work +world +worry +worth +wrap +wreck +wrestle +wrist +write +wrong +yard +year +yellow +you +young +youth +zebra +zero +zone +zoo diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3f3a0dd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +version: '3.8' + +services: + stegocrypt: + build: . + container_name: stegocrypt + ports: + - "5000:5000" + environment: + - FLASK_ENV=production + # Uncomment to persist uploads between restarts: + # volumes: + # - ./uploads:/tmp/stego_uploads + restart: unless-stopped + deploy: + resources: + limits: + memory: 512M # Argon2 needs 256MB per operation + reservations: + memory: 256M + + # Optional: Add nginx reverse proxy for production + # nginx: + # image: nginx:alpine + # ports: + # - "80:80" + # - "443:443" + # volumes: + # - ./nginx.conf:/etc/nginx/nginx.conf:ro + # - ./certs:/etc/nginx/certs:ro + # depends_on: + # - stegocrypt + # restart: unless-stopped diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..982a0d7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +# Core dependencies +flask>=3.0.0 +gunicorn>=21.0.0 +pillow>=10.0.0 +cryptography>=41.0.0 + +# Memory-hard key derivation (highly recommended) +argon2-cffi>=23.0.0 + +# Optional: For production deployment +# gevent>=23.0.0 diff --git a/secureDeleter.py b/secureDeleter.py new file mode 100644 index 0000000..7956f9b --- /dev/null +++ b/secureDeleter.py @@ -0,0 +1,65 @@ +import os +import shutil +import random + +class SecureDeleter: + def __init__(self, path): + self.path = path + + def overwrite_file(self, file_path, passes=7): + """Overwrites the file with multiple patterns and random data.""" + patterns = [b'\x00', b'\xFF', random.randbytes(1)] + print(patterns) + length = os.path.getsize(file_path) + for _ in range(passes): + with open(file_path, "r+b") as file: + for pattern in patterns: + file.seek(0) + for _ in range(length): + file.write(pattern) + # Final pass with random data for each byte + file.seek(0) + file.write(random.randbytes(length)) + + def delete_file(self, file_path): + """Securely deletes a file.""" + if os.path.isfile(file_path): + self.overwrite_file(file_path) + os.remove(file_path) + + def delete_folder(self, folder_path): + """Securely deletes a folder and its contents.""" + if os.path.isdir(folder_path): + for root, dirs, files in os.walk(folder_path, topdown=False): + for name in files: + file_path = os.path.join(root, name) + self.delete_file(file_path) + for name in dirs: + os.rmdir(os.path.join(root, name)) + shutil.rmtree(folder_path)\ + + def wipe_free_space(self, drive, size=1024*1024*10): # 10MB default file size + """Writes temporary files with random data to overwrite free disk space.""" + temp_files = [] + try: + while True: + temp_file = os.path.join(drive, f"temp_{random.randint(0, 999999)}.dat") + with open(temp_file, "wb") as file: + file.write(random.randbytes(size)) + temp_files.append(temp_file) + except OSError: # Typically disk full + for temp_file in temp_files: + os.remove(temp_file) + + def execute(self): + """Determines whether the path is a file or folder and deletes it securely.""" + if os.path.isfile(self.path): + self.delete_file(self.path) + elif os.path.isdir(self.path): + self.delete_folder(self.path) + else: + print("Path does not exist.") + +# Usage example: +#deleter = SecureDeleter("/path/to/your/file_or_folder") +#deleter.execute() diff --git a/static/.gitkeep b/static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/about.html b/templates/about.html new file mode 100644 index 0000000..9379109 --- /dev/null +++ b/templates/about.html @@ -0,0 +1,180 @@ +{% extends "base.html" %} + +{% block title %}About - Stegasoo{% endblock %} + +{% block content %} +
+
+
+
+
About Stegasoo
+
+
+

+ Stegasoo is a hybrid steganography system that hides encrypted messages inside + ordinary images. It combines multiple security layers to create a system that is + both highly secure and practical to use. +

+ +
System Status
+
+
+
+ {% if has_argon2 %} + +
+ Argon2id Available +
Memory-hard key derivation (256MB)
+
+ {% else %} + +
+ Using PBKDF2 Fallback +
Install argon2-cffi for better security
+
+ {% endif %} +
+
+
+
+ +
+ AES-256-GCM +
Authenticated encryption enabled
+
+
+
+
+
+
+ +
+
+
Security Model
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ComponentEntropyPurpose
Reference Photo~80-256 bitsSomething you have (plausible deniability)
3-Word Phrase~33 bitsSomething you know (changes daily)
6-Digit PIN~20 bitsSomething you know (static)
DateN/AAutomatic key rotation
Combined133+ bitsBeyond brute force
+
+
+
+ +
+
+
Attack Resistance
+
+
+
+
+
What Attackers Can't Do
+
    +
  • + + Brute force the passphrase (2133 combinations) +
  • +
  • + + Use rainbow tables (random salt per message) +
  • +
  • + + Detect hidden data (random pixel selection) +
  • +
  • + + Use GPU farms (Argon2 requires 256MB RAM per attempt) +
  • +
+
+
+
Real Threats
+
    +
  • + + Social engineering (someone tricks you) +
  • +
  • + + Physical access to your devices +
  • +
  • + + Malware/keyloggers on your system +
  • +
  • + + Shoulder surfing while you type +
  • +
+
+
+
+
+ +
+
+
Best Practices
+
+
+
+
+
Do
+
    +
  • Memorize your phrases and PIN, never write them down
  • +
  • Use a reference photo that both parties already have
  • +
  • Use different carrier images for each message
  • +
  • Share stego images through normal channels (looks innocent)
  • +
+
+
+
Don't
+
    +
  • Don't transmit the reference photo
  • +
  • Don't reuse the same carrier image
  • +
  • Don't store phrases or PIN digitally
  • +
  • Don't resize or recompress stego images
  • +
+
+
+
+
+
+
+{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..abfd4ca --- /dev/null +++ b/templates/base.html @@ -0,0 +1,180 @@ + + + + + + {% block title %}Stegasoo{% endblock %} + + + + + + + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} + + {% block content %}{% endblock %} +
+ + + + + {% block scripts %}{% endblock %} + + diff --git a/templates/decode.html b/templates/decode.html new file mode 100644 index 0000000..df59dee --- /dev/null +++ b/templates/decode.html @@ -0,0 +1,122 @@ +{% extends "base.html" %} + +{% block title %}Decode Message - Stegasoo{% endblock %} + +{% block content %} +
+
+
+
+
Decode Secret Message
+
+
+ {% if decoded_message %} +
+
Message Decrypted Successfully!
+
+ +
+ +
{{ decoded_message }}
+
+ + + Decode Another Message + + + {% else %} + +
+
+
+ + +
+ The same reference photo used for encoding +
+
+ +
+ + +
+ The image containing the hidden message +
+
+
+ +
+
+ + +
+ The phrase for the day the message was encoded +
+
+ +
+ + +
+ Your static 6-digit PIN +
+
+
+ + +
+ + {% endif %} +
+
+ +
+
+
Troubleshooting
+
    +
  • + + Make sure you're using the exact same reference photo file +
  • +
  • + + Use the phrase for the day the message was encoded, not today +
  • +
  • + + Ensure the stego image hasn't been resized or recompressed +
  • +
  • + + Double-check your PIN is correct +
  • +
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/encode.html b/templates/encode.html new file mode 100644 index 0000000..99179a6 --- /dev/null +++ b/templates/encode.html @@ -0,0 +1,130 @@ +{% extends "base.html" %} + +{% block title %}Encode Message - Stegasoo{% endblock %} + +{% block content %} +
+
+
+
+
Encode Secret Message
+
+
+
+
+
+ + +
+ The secret photo both parties have (NOT transmitted) +
+
+ +
+ + +
+ The image to hide your message in (e.g., a meme) +
+
+
+ +
+ + +
+ +
+
+ + +
+ Your 3-word phrase for today (from your phrase card) +
+
+ +
+ + +
+ Your static 6-digit PIN +
+
+
+ + +
+ +
+ +
+
+ + AES-256-GCM Encryption +
+
+ + Random Pixel Embedding +
+
+ + Undetectable by Analysis +
+
+ +
+ + Limits: + Carrier image max ~4 megapixels (2000×2000). + Files max 5MB each. + Message max 50KB. +
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/generate.html b/templates/generate.html new file mode 100644 index 0000000..555d04e --- /dev/null +++ b/templates/generate.html @@ -0,0 +1,181 @@ +{% extends "base.html" %} + +{% block title %}Generate Phrase Card - Stegasoo{% endblock %} + +{% block content %} +
+
+
+
+
Generate Phrase Card + PIN
+
+
+ {% if not generated %} +

+ Generate your weekly phrase card and static PIN. Customize your security level: +

+ +
+
+
+ + +
More words = more security, harder to memorize
+
+ +
+ + +
Same PIN used every day
+
+
+ +
+
+ Estimated phrase+PIN entropy: + ~53 bits +
+
+
+
+ + Good for most use cases + • Reference photo adds ~80-256 bits more + +
+ + +
+ {% else %} + +
+ + Memorize this information, then close this page! + Do not save or screenshot. Refresh to generate new credentials. +
+ +
+
YOUR STATIC PIN
+
{{ pin }}
+ Use this {{ pin_length }}-digit PIN every day +
+ +
+ +
DAILY PHRASES ({{ words_per_phrase }} words each)
+ +
+ + + + + + + + + {% for day in days %} + + + + + {% endfor %} + +
DayPhrase
+ {{ day }} + + {{ phrases[day] }} +
+
+ +
+
Security Summary
+
+
+
{{ phrase_entropy }}
+ bits/phrase +
+
+
{{ pin_entropy }}
+ bits/PIN +
+
+
{{ total_entropy }}
+ bits total +
+
+ + + reference photo (~80-256 bits) = {{ total_entropy + 80 }}+ bits combined + +
+ +
+
Memorization Tip
+

+ Total to memorize: {{ words_per_phrase * 7 }} words + {{ pin_length }} digits +

+

+ Create a story for each day: "On Monday, the [word1] and [word2] went to see [word3]..." +

+
+ + + Generate New Credentials + + + {% endif %} +
+
+
+
+{% endblock %} + +{% block scripts %} +{% if not generated %} + +{% endif %} +{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..2005285 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,108 @@ +{% extends "base.html" %} + +{% block title %}Stegasoo - Secure Steganography{% endblock %} + +{% block content %} +
+ +

Stegasoo

+

Hide encrypted messages in plain sight using advanced steganography

+
+ +
+
+
+
+ +
+
+
Generate Keys
+

+ Create your weekly phrase card and PIN. Memorize 21 words + 6 digits for maximum security. +

+ + Generate + +
+
+
+ +
+
+
+ +
+
+
Encode Message
+

+ Hide your secret message inside an innocent-looking image using your phrase + PIN. +

+ + Encode + +
+
+
+ +
+
+
+ +
+
+
Decode Message
+

+ Extract and decrypt hidden messages from stego images using your credentials. +

+ + Decode + +
+
+
+
+ +
+
+
How It Works
+
+
+
+
+
Key Components
+
    +
  • + + Reference Photo — Any photo you and recipient both have +
  • +
  • + + Day Phrase — 3 words, different each day of the week +
  • +
  • + + Static PIN — 6 digits, same every day +
  • +
+
+
+
Security Features
+
    +
  • + + Argon2id memory-hard key derivation (256MB) +
  • +
  • + + Pseudo-random pixel selection (defeats steganalysis) +
  • +
  • + + AES-256-GCM authenticated encryption +
  • +
+
+
+
+
+{% endblock %} diff --git a/uploads/.gitkeep b/uploads/.gitkeep new file mode 100644 index 0000000..e69de29