From 4e6c5b44010f5fb759b536de801bca4540a7a291 Mon Sep 17 00:00:00 2001 From: "Aaron D. Lee" Date: Sat, 27 Dec 2025 16:36:30 -0500 Subject: [PATCH] More tweaks --- Dockerfile | 7 +- README.md | 64 +++++------ app.py | 16 ++- docker-compose.yml | 6 +- requirements-ml.txt | 8 ++ requirements.txt | 5 + story_generator.py | 244 ++++++++++++++++++++++++++++++++++++++++ templates/base.html | 25 +++- templates/generate.html | 53 ++++++++- 9 files changed, 380 insertions(+), 48 deletions(-) create mode 100644 requirements-ml.txt create mode 100644 story_generator.py diff --git a/Dockerfile b/Dockerfile index bf41094..9b67308 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,8 +15,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* # Install Python dependencies + COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt +#COPY requirements-ml.txt . + +RUN pip install --upgrade pip +RUN pip install --no-cache-dir -r requirements.txt --root-user-action=ignore +#RUN pip install --no-cache-dir -r requirements-ml.txt # Copy application COPY . . diff --git a/README.md b/README.md index 39f9f74..c2113ad 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# StegoCrypt Web Service +# Stegasoo Web Service A containerized Flask + Bootstrap web UI for hybrid Photo + Day-Phrase + PIN steganography. @@ -12,10 +12,11 @@ A containerized Flask + Bootstrap web UI for hybrid Photo + Day-Phrase + PIN ste - 🔐 **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 +- 📅 **Daily key rotation** with customizable phrases (3-12 words) +- 🔢 **Static PIN** for additional entropy (6-8 digits) - 🖼️ **Reference photo** as "something you have" - 🌐 **Web UI** with Bootstrap 5 dark theme +- 📖 **Memory aid stories** to help memorize phrases (template or AI-powered) ## Quick Start @@ -39,6 +40,9 @@ source venv/bin/activate # Linux/Mac # Install dependencies pip install -r requirements.txt +# Optional: Enable AI-powered story generation +pip install -r requirements-ml.txt + # Run development server python app.py @@ -51,8 +55,9 @@ gunicorn --bind 0.0.0.0:5000 app:app ### 1. Generate Credentials Visit `/generate` to create: -- **7 three-word phrases** (one per day of week) -- **1 six-digit PIN** (same every day) +- **7 phrases** (one per day of week, 3-12 words each) +- **1 PIN** (6-8 digits, same every day) +- **Memory aid stories** (optional, helps memorize phrases) Memorize these! Don't save them. @@ -62,8 +67,8 @@ 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 +- **Day phrase** - Today's phrase +- **PIN** - Your static PIN Download the stego image and share it through any channel. @@ -80,28 +85,30 @@ Visit `/decode` and provide: | 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** | +| Day Phrase | ~33-132 bits | Something you know (rotates daily) | +| PIN | ~20-27 bits | Something you know (static) | +| **Combined** | **133-415+ bits** | **Beyond brute force** | ### Attack Resistance | Attack | Result | |--------|--------| -| Brute force | 2^133 combinations = impossible | +| 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 +## Memory Aid Stories -| 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 | +The generate page can create stories to help you memorize your phrases: + +**Template-based** (default): +> Monday morning began when I discovered a **APPLE** near the **FOREST**. I had to **THUNDER** quickly, then grab the **CRYSTAL** before reaching the **BRAVE**. + +**AI-powered** (with `requirements-ml.txt`): +- Uses DistilGPT-2 (~350MB model) +- Generates more coherent, natural stories +- Words highlighted in RED CAPS ## Configuration @@ -121,25 +128,6 @@ For production, consider: 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. diff --git a/app.py b/app.py index e0bba00..4cd36a5 100644 --- a/app.py +++ b/app.py @@ -25,6 +25,7 @@ from flask import Flask, render_template, request, send_file, jsonify, flash, re from werkzeug.utils import secure_filename from PIL import Image from secureDeleter import SecureDeleter +from story_generator import generate_all_stories, HAS_ML from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend @@ -445,6 +446,10 @@ def generate(): words_per_phrase = int(request.form.get('words_per_phrase', 3)) pin_length = int(request.form.get('pin_length', 6)) + # Disable generate_stories for now (until much better) + #generate_stories = request.form.get('generate_stories') == 'on' + generate_stories = request.form.get('generate_stories') == 'off' + # Clamp values to valid ranges words_per_phrase = max(3, min(12, words_per_phrase)) pin_length = max(6, min(8, pin_length)) @@ -457,6 +462,11 @@ def generate(): pin_entropy = int(pin_length * 3.32) # log2(10) ≈ 3.32 bits per digit total_entropy = phrase_entropy + pin_entropy + # Generate memory aid stories if requested + stories = None + if generate_stories: + stories = generate_all_stories(phrases, use_ml=HAS_ML) + return render_template('generate.html', phrases=phrases, pin=pin, @@ -466,8 +476,10 @@ def generate(): pin_length=pin_length, phrase_entropy=phrase_entropy, pin_entropy=pin_entropy, - total_entropy=total_entropy) - return render_template('generate.html', generated=False) + total_entropy=total_entropy, + stories=stories, + has_ml=HAS_ML) + return render_template('generate.html', generated=False, has_ml=HAS_ML) @app.route('/encode', methods=['GET', 'POST']) diff --git a/docker-compose.yml b/docker-compose.yml index 3f3a0dd..900ff3b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,9 @@ version: '3.8' services: - stegocrypt: + stegasoo: build: . - container_name: stegocrypt + container_name: stegasoo ports: - "5000:5000" environment: @@ -29,5 +29,5 @@ services: # - ./nginx.conf:/etc/nginx/nginx.conf:ro # - ./certs:/etc/nginx/certs:ro # depends_on: - # - stegocrypt + # - stegasoo # restart: unless-stopped diff --git a/requirements-ml.txt b/requirements-ml.txt new file mode 100644 index 0000000..0933ed8 --- /dev/null +++ b/requirements-ml.txt @@ -0,0 +1,8 @@ +# ML dependencies for AI-powered story generation +# Install with: pip install -r requirements-ml.txt +# +# Note: These add ~1-2GB disk space for model downloads +# The app works without these (falls back to template-based stories) + +transformers>=4.35.0 +torch>=2.0.0 diff --git a/requirements.txt b/requirements.txt index 982a0d7..9605ef7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,10 @@ cryptography>=41.0.0 # Memory-hard key derivation (highly recommended) argon2-cffi>=23.0.0 +# Optional: ML story generation (adds ~1GB disk space) +# Uncomment for AI-powered memory aid stories +# transformers>=4.35.0 +# torch>=2.0.0 + # Optional: For production deployment # gevent>=23.0.0 diff --git a/story_generator.py b/story_generator.py new file mode 100644 index 0000000..a7a10b6 --- /dev/null +++ b/story_generator.py @@ -0,0 +1,244 @@ +""" +Story Generator for Passphrase Memorization +Uses lightweight ML (DistilGPT-2) for coherent stories, with template fallback. +""" + +import random +import re +from typing import Optional + +# Try to import ML libraries +try: + from transformers import pipeline, set_seed + import torch + HAS_ML = True +except ImportError: + HAS_ML = False + +# Global generator (lazy loaded) +_generator = None +_model_loaded = False + + +def get_generator(): + """Lazy load the text generation model.""" + global _generator, _model_loaded + + if not HAS_ML: + return None + + if not _model_loaded: + try: + # Use distilgpt2 - small (~350MB) and fast + device = 0 if torch.cuda.is_available() else -1 + _generator = pipeline( + 'text-generation', + model='distilgpt2', + device=device, + torch_dtype=torch.float32 + ) + _model_loaded = True + print("ML story generator loaded successfully") + except Exception as e: + print(f"Could not load ML model: {e}. Using templates.") + _generator = None + _model_loaded = True # Don't retry + + return _generator + + +def generate_story_ml(day: str, words: list[str], max_attempts: int = 3) -> Optional[str]: + """ + Generate a story using ML that incorporates all passphrase words. + + Returns None if ML is unavailable or generation fails. + """ + generator = get_generator() + if generator is None: + return None + + # Create a compelling prompt + words_str = ', '.join(words[:-1]) + f', and {words[-1]}' if len(words) > 1 else words[0] + + prompts = [ + f"{day}, something memorable happened including: {words_str}.", + ] + + prompt = random.choice(prompts) + + try: + set_seed(random.randint(0, 10000)) + + # Generate text + result = generator( + prompt, + max_new_tokens=80, + num_return_sequences=1, + temperature=0.8, + top_p=0.9, + do_sample=True, + pad_token_id=50256, # eos token for gpt2 + ) + + story = result[0]['generated_text'] + + # Clean up - get just a few sentences + story = story.strip() + + # Try to end at a sentence boundary + for end_char in ['. ', '! ', '? ']: + last_end = story.rfind(end_char) + if last_end > len(prompt) + 20: + story = story[:last_end + 1] + break + + # Verify most words are present (ML doesn't always include all) + story_lower = story.lower() + words_found = sum(1 for w in words if w.lower() in story_lower) + + if words_found < len(words) * 0.5: # At least 50% of words + # Append missing words naturally + missing = [w for w in words if w.lower() not in story_lower] + if missing: + story += f" Don't forget: {', '.join(missing)}." + + return story + + except Exception as e: + print(f"ML generation error: {e}") + return None + + +# ============================================================================ +# TEMPLATE FALLBACK (always available) +# ============================================================================ + +STORY_TEMPLATES = { + 'Monday': [ + "Monday morning began when I discovered a {0} near the {1}. I had to {2} quickly, then grab the {3} before reaching the {4}.", + "The week started with a {0} appearing at the {1}. My plan was to {2}, secure the {3}, and head toward the {4}.", + "On Monday, the {0} and the {1} crossed paths. We decided to {2}, bring the {3}, and meet at the {4}.", + ], + 'Tuesday': [ + "Tuesday brought a {0} to the {1}. Everyone wanted to {2}, especially with the {3} near the {4}.", + "The {0} arrived Tuesday carrying a {1}. Together we would {2}, protect the {3}, and explore the {4}.", + "On Tuesday, my {0} transformed into a {1}. I needed to {2}, find the {3}, and unlock the {4}.", + ], + 'Wednesday': [ + "By Wednesday, the {0} had found a {1}. The mission: {2}, retrieve the {3}, and guard the {4}.", + "Midweek magic: a {0} emerged from the {1}. We had to {2}, grab the {3}, and escape to the {4}.", + "Wednesday's {0} was hiding near the {1}. To {2} successfully, we needed the {3} and the {4}.", + ], + 'Thursday': [ + "Thursday's {0} came with a {1}. Our plan: {2}, then move the {3} inside the {4}.", + "On Thursday, the {0} met the {1} unexpectedly. They decided to {2}, share the {3}, and visit the {4}.", + "The {0} adventure on Thursday led us to a {1}. We chose to {2}, carry the {3}, and discover the {4}.", + ], + 'Friday': [ + "Friday arrived with a {0} and a {1}. Time to {2}, celebrate with the {3}, and toast the {4}!", + "TGIF! The {0} party featured a {1}. We would {2}, enjoy the {3}, and dance around the {4}.", + "Friday's surprise was a {0} inside a {1}. Everyone wanted to {2}, taste the {3}, and admire the {4}.", + ], + 'Saturday': [ + "Saturday morning, the {0} journeyed to the {1}. Goals: {2}, collect the {3}, and protect the {4}.", + "Weekend mode: a {0} relaxing near a {1}. I chose to {2}, photograph the {3}, and sketch the {4}.", + "On Saturday, the legendary {0} appeared at the {1}. Heroes must {2}, wield the {3}, and defeat the {4}.", + ], + 'Sunday': [ + "Sunday peace was broken by a {0} and a {1}. We needed to {2}, fix the {3}, and restore the {4}.", + "A quiet Sunday with my {0} near the {1}. Plans: {2} later, maybe find the {3}, or visit the {4}.", + "Sunday sunset revealed a {0} beside a {1}. Time to {2}, remember the {3}, and dream of the {4}.", + ], +} + +# Extensions for 6+ word phrases +EXTENSIONS = [ + [" Suddenly, a {5} appeared!"], + [" The {6} changed everything."], + [" Behind it was a {7}."], + [" Plus a mysterious {8}."], + [" The {9} completed the quest."], + [" A {10} watched from afar."], + [" And finally, the legendary {11}."], +] + + +def generate_story_template(day: str, words: list[str]) -> str: + """Generate story using templates (fallback method).""" + templates = STORY_TEMPLATES.get(day, STORY_TEMPLATES['Monday']) + template = random.choice(templates) + + # Add extensions for longer phrases + for i, ext_list in enumerate(EXTENSIONS): + word_idx = i + 5 + if len(words) > word_idx: + template += random.choice(ext_list) + + # Pad words list to ensure we have enough for any template + padded_words = words + [''] * (12 - len(words)) + + return template.format(*padded_words) + + +# ============================================================================ +# MAIN API +# ============================================================================ + +def generate_story(day: str, words: list[str], use_ml: bool = True) -> dict: + """ + Generate a memorable story incorporating the passphrase words. + + Args: + day: Day of the week (e.g., 'Monday') + words: List of passphrase words + use_ml: Whether to try ML generation first + + Returns: + dict with 'story' (plain text) and 'story_html' (with highlighted words) + """ + story = None + used_ml = False + + # Try ML first if requested + if use_ml and HAS_ML: + story = generate_story_ml(day, words) + if story: + used_ml = True + + # Fall back to templates + if story is None: + story = generate_story_template(day, words) + + # Generate HTML version with highlighted words (RED and CAPS) + html_story = story + for word in words: + # Case-insensitive replacement with highlighted version + pattern = re.compile(re.escape(word), re.IGNORECASE) + html_story = pattern.sub( + f'{word.upper()}', + html_story + ) + + return { + 'story': story, + 'story_html': html_story, + 'used_ml': used_ml + } + + +def generate_all_stories(phrases: dict[str, str], use_ml: bool = True) -> dict[str, dict]: + """ + Generate stories for all days. + + Args: + phrases: Dict mapping day names to phrase strings + use_ml: Whether to use ML generation + + Returns: + Dict mapping day names to story dicts + """ + stories = {} + for day, phrase in phrases.items(): + words = phrase.split() + stories[day] = generate_story(day, words, use_ml=use_ml) + return stories diff --git a/templates/base.html b/templates/base.html index d19a188..12e7efe 100644 --- a/templates/base.html +++ b/templates/base.html @@ -97,10 +97,33 @@ word-spacing: 0.3rem; } + .story-word { + color: #ff6b6b; + font-weight: bold; + text-transform: uppercase; + } + + .story-card { + background: rgba(0, 0, 0, 0.2); + border-left: 3px solid var(--gradient-start); + padding: 1rem; + margin-bottom: 0.75rem; + border-radius: 0.5rem; + font-size: 0.95rem; + line-height: 1.6; + } + + .story-card .day-label { + font-weight: bold; + color: var(--gradient-start); + margin-bottom: 0.5rem; + } + .pin-display { font-family: 'Courier New', monospace; + color: #ff9900; font-size: 2rem; - letter-spacing: 0.5rem; + letter-spacing: 0.3rem; background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; diff --git a/templates/generate.html b/templates/generate.html index cb1acd9..87b7cf9 100644 --- a/templates/generate.html +++ b/templates/generate.html @@ -59,18 +59,33 @@ - {% else %} +
+ + Credentials Generated! - Refresh to generate new credentials +
+
- Memorize this information, then close this page! - Do not save or screenshot. Refresh to generate new credentials. + Memorize the information then close! - Do not save/screenshot
+
+
YOUR STATIC PIN
{{ pin }}
@@ -135,6 +150,26 @@

+ {% if stories %} +
+ +
+ MEMORY AID STORIES + {% if has_ml %}AI-generated{% else %}Template-based{% endif %} +
+

+ Passphrase words are shown in RED CAPS. + Read each story to help memorize your phrases. +

+ + {% for day in days %} +
+
{{ day }}
+
{{ stories[day].story_html|safe }}
+
+ {% endfor %} + {% endif %} + Generate New Credentials @@ -176,6 +211,18 @@ function updateEntropy() { document.getElementById('wordsSelect').addEventListener('change', updateEntropy); document.getElementById('pinSelect').addEventListener('change', updateEntropy); + +// Loading state for generate button +document.querySelector('form').addEventListener('submit', function() { + const btn = document.getElementById('generateBtn'); + const storiesChecked = document.getElementById('generateStories').checked; + btn.disabled = true; + if (storiesChecked) { + btn.innerHTML = 'Generating stories...'; + } else { + btn.innerHTML = 'Generating...'; + } +}); {% endif %} {% endblock %}