diff --git a/Dockerfile.txt b/Dockerfile.txt deleted file mode 100644 index edcb203..0000000 --- a/Dockerfile.txt +++ /dev/null @@ -1,130 +0,0 @@ -# Stegasoo Docker Image -# Multi-stage build for smaller image size - -FROM python:3.11-slim as base - -# Set environment variables -ENV PYTHONDONTWRITEBYTECODE=1 -ENV PYTHONUNBUFFERED=1 - -# Install system dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ - gcc \ - libc-dev \ - libffi-dev \ - libzbar0 \ - && rm -rf /var/lib/apt/lists/* - -# ============================================================================ -# Builder stage - install Python packages -# ============================================================================ -FROM base as builder - -WORKDIR /build - -# Copy package files (including README.md which pyproject.toml references) -COPY pyproject.toml README.md ./ -COPY src/ src/ -COPY data/ data/ - -# Install the package with web extras -RUN pip install --no-cache-dir ".[web]" - -# ============================================================================ -# Production stage - Web UI -# ============================================================================ -FROM base as web - -WORKDIR /app - -# Copy installed packages from builder -COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages -COPY --from=builder /usr/local/bin /usr/local/bin - -# Copy application files -COPY src/ src/ -COPY data/ data/ -COPY frontends/web/ frontends/web/ - -# Create upload directory -RUN mkdir -p /tmp/stego_uploads - -# Create non-root user -RUN useradd -m -u 1000 stego && chown -R stego:stego /app /tmp/stego_uploads -USER stego - -# Set Python path -ENV PYTHONPATH=/app/src - -# 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 -WORKDIR /app/frontends/web -CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "--threads", "4", "--timeout", "60", "app:app"] - -# ============================================================================ -# API stage - REST API -# ============================================================================ -FROM base as api - -WORKDIR /app - -# Install API extras -COPY pyproject.toml README.md ./ -COPY src/ src/ -COPY data/ data/ -RUN pip install --no-cache-dir ".[api]" - -# Copy API files -COPY frontends/api/ frontends/api/ - -# Create non-root user -RUN useradd -m -u 1000 stego && chown -R stego:stego /app -USER stego - -# Set Python path -ENV PYTHONPATH=/app/src - -# Expose port -EXPOSE 8000 - -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/')" || exit 1 - -# Run with uvicorn -WORKDIR /app/frontends/api -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] - -# ============================================================================ -# CLI stage - Command line tool -# ============================================================================ -FROM base as cli - -WORKDIR /app - -# Install CLI extras -COPY pyproject.toml README.md ./ -COPY src/ src/ -COPY data/ data/ -RUN pip install --no-cache-dir ".[cli]" - -# Copy CLI files -COPY frontends/cli/ frontends/cli/ - -# Create non-root user -RUN useradd -m -u 1000 stego && chown -R stego:stego /app -USER stego - -# Set Python path -ENV PYTHONPATH=/app/src - -# Default to help -WORKDIR /app/frontends/cli -ENTRYPOINT ["python", "main.py"] -CMD ["--help"] diff --git a/Dockerfile_old b/Dockerfile_old deleted file mode 100644 index edcb203..0000000 --- a/Dockerfile_old +++ /dev/null @@ -1,130 +0,0 @@ -# Stegasoo Docker Image -# Multi-stage build for smaller image size - -FROM python:3.11-slim as base - -# Set environment variables -ENV PYTHONDONTWRITEBYTECODE=1 -ENV PYTHONUNBUFFERED=1 - -# Install system dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ - gcc \ - libc-dev \ - libffi-dev \ - libzbar0 \ - && rm -rf /var/lib/apt/lists/* - -# ============================================================================ -# Builder stage - install Python packages -# ============================================================================ -FROM base as builder - -WORKDIR /build - -# Copy package files (including README.md which pyproject.toml references) -COPY pyproject.toml README.md ./ -COPY src/ src/ -COPY data/ data/ - -# Install the package with web extras -RUN pip install --no-cache-dir ".[web]" - -# ============================================================================ -# Production stage - Web UI -# ============================================================================ -FROM base as web - -WORKDIR /app - -# Copy installed packages from builder -COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages -COPY --from=builder /usr/local/bin /usr/local/bin - -# Copy application files -COPY src/ src/ -COPY data/ data/ -COPY frontends/web/ frontends/web/ - -# Create upload directory -RUN mkdir -p /tmp/stego_uploads - -# Create non-root user -RUN useradd -m -u 1000 stego && chown -R stego:stego /app /tmp/stego_uploads -USER stego - -# Set Python path -ENV PYTHONPATH=/app/src - -# 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 -WORKDIR /app/frontends/web -CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "--threads", "4", "--timeout", "60", "app:app"] - -# ============================================================================ -# API stage - REST API -# ============================================================================ -FROM base as api - -WORKDIR /app - -# Install API extras -COPY pyproject.toml README.md ./ -COPY src/ src/ -COPY data/ data/ -RUN pip install --no-cache-dir ".[api]" - -# Copy API files -COPY frontends/api/ frontends/api/ - -# Create non-root user -RUN useradd -m -u 1000 stego && chown -R stego:stego /app -USER stego - -# Set Python path -ENV PYTHONPATH=/app/src - -# Expose port -EXPOSE 8000 - -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/')" || exit 1 - -# Run with uvicorn -WORKDIR /app/frontends/api -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] - -# ============================================================================ -# CLI stage - Command line tool -# ============================================================================ -FROM base as cli - -WORKDIR /app - -# Install CLI extras -COPY pyproject.toml README.md ./ -COPY src/ src/ -COPY data/ data/ -RUN pip install --no-cache-dir ".[cli]" - -# Copy CLI files -COPY frontends/cli/ frontends/cli/ - -# Create non-root user -RUN useradd -m -u 1000 stego && chown -R stego:stego /app -USER stego - -# Set Python path -ENV PYTHONPATH=/app/src - -# Default to help -WORKDIR /app/frontends/cli -ENTRYPOINT ["python", "main.py"] -CMD ["--help"] diff --git a/init_head.py b/init_head.py deleted file mode 100644 index 7a2a3bf..0000000 --- a/init_head.py +++ /dev/null @@ -1,621 +0,0 @@ -""" -Stegasoo - Secure Steganography Library - -A Python library for hiding encrypted messages and files in images using -hybrid photo + passphrase + PIN authentication. - -Basic Usage - Text Message: - from stegasoo import encode, decode, generate_credentials - - # Generate credentials - creds = generate_credentials(use_pin=True, use_rsa=False) - print(creds.phrases['Monday']) - print(creds.pin) - - # Encode a message - with open('secret.jpg', 'rb') as f: - ref_photo = f.read() - with open('meme.png', 'rb') as f: - carrier = f.read() - - result = encode( - message="Meet at midnight", - reference_photo=ref_photo, - carrier_image=carrier, - day_phrase="apple forest thunder", - pin="123456" - ) - - with open('stego.png', 'wb') as f: - f.write(result.stego_image) - - # Decode a message - decoded = decode( - stego_image=result.stego_image, - reference_photo=ref_photo, - day_phrase="apple forest thunder", - pin="123456" - ) - print(decoded.message) # "Meet at midnight" - -File Embedding: - from stegasoo import encode_file, decode, FilePayload - - # Encode a file - result = encode_file( - filepath="secret_document.pdf", - reference_photo=ref_photo, - carrier_image=carrier, - day_phrase="apple forest thunder", - pin="123456" - ) - - # Decode - automatically detects file vs text - decoded = decode(...) - if decoded.is_file: - with open(decoded.filename, 'wb') as f: - f.write(decoded.file_data) - else: - print(decoded.message) - -Debugging: - from stegasoo.debug import debug - debug.enable(True) # Enable debug output - debug.enable_performance(True) # Enable timing -""" - -from .constants import __version__, DAY_NAMES, MAX_MESSAGE_SIZE, MAX_FILE_PAYLOAD_SIZE -from .models import ( - Credentials, - EncodeInput, - EncodeResult, - DecodeInput, - DecodeResult, - EmbedStats, - KeyInfo, - ValidationResult, - FilePayload, -) -from .exceptions import ( - StegasooError, - ValidationError, - PinValidationError, - MessageValidationError, - ImageValidationError, - KeyValidationError, - SecurityFactorError, - CryptoError, - EncryptionError, - DecryptionError, - KeyDerivationError, - KeyGenerationError, - KeyPasswordError, - SteganographyError, - CapacityError, - ExtractionError, - EmbeddingError, - InvalidHeaderError, -) -from .keygen import ( - generate_credentials, - generate_pin, - generate_phrase, - generate_day_phrases, - generate_rsa_key, - export_rsa_key_pem, - load_rsa_key, - get_key_info, -) -from .validation import ( - validate_pin, - validate_message, - validate_payload, - validate_file_payload, - validate_image, - validate_rsa_key, - validate_security_factors, - validate_phrase, - validate_date_string, - require_valid_pin, - require_valid_message, - require_valid_payload, - require_valid_image, - require_valid_rsa_key, - require_security_factors, -) -from .crypto import ( - encrypt_message, - decrypt_message, - decrypt_message_text, - derive_hybrid_key, - derive_pixel_key, - hash_photo, - parse_header, - get_date_from_encrypted, - has_argon2, -) -from .steganography import ( - embed_in_image, - extract_from_image, - calculate_capacity, - get_image_dimensions, - get_image_format, - is_lossless_format, - LOSSLESS_FORMATS, -) -from .utils import ( - generate_filename, - parse_date_from_filename, - get_day_from_date, - get_today_date, - get_today_day, - secure_delete, - SecureDeleter, - format_file_size, -) -from .debug import debug # Import debug utilities - -# QR Code utilities (optional, depends on qrcode and pyzbar) -try: - from .qr_utils import ( - generate_qr_code, - read_qr_code, - read_qr_code_from_file, - extract_key_from_qr, - extract_key_from_qr_file, - compress_data, - decompress_data, - auto_decompress, - normalize_pem, - is_compressed, - can_fit_in_qr, - needs_compression, - has_qr_read, - has_qr_write, - has_qr_support, - ) - HAS_QR_UTILS = True -except ImportError: - HAS_QR_UTILS = False - -from datetime import date -from pathlib import Path -from typing import Optional, Union, Dict, Any - - -def encode( - message: Union[str, bytes, FilePayload], - reference_photo: bytes, - carrier_image: bytes, - day_phrase: str, - pin: str = "", - rsa_key_data: Optional[bytes] = None, - rsa_password: Optional[str] = None, - date_str: Optional[str] = None, - output_format: Optional[str] = None, -) -> EncodeResult: - """ - Encode a secret message or file into an image. - - High-level convenience function that handles validation, - encryption, and embedding in one call. - - Args: - message: Secret message (str), raw bytes, or FilePayload to hide - reference_photo: Shared reference photo bytes - carrier_image: Image to hide message in - day_phrase: Today's passphrase - pin: Static PIN (optional if using RSA key) - rsa_key_data: RSA private key PEM bytes (optional if using PIN) - rsa_password: Password for RSA key if encrypted - date_str: Date string YYYY-MM-DD (defaults to today) - output_format: Force output format ('PNG', 'BMP'). If None, preserves - carrier format for lossless types, defaults to PNG for lossy. - - Returns: - EncodeResult with stego image and metadata - - Raises: - ValidationError: If inputs are invalid - SecurityFactorError: If no PIN or RSA key provided - CapacityError: If carrier is too small - EncryptionError: If encryption fails - - Note: - Output format is always lossless (PNG or BMP) to preserve hidden data. - If carrier is JPEG/GIF, output will be PNG to maintain data integrity. - """ - # Debug logging - debug.print(f"encode called: message type={type(message).__name__}, " - f"day_phrase='{day_phrase[:20]}...', pin_length={len(pin)}") - - # Validate inputs - require_valid_payload(message) - require_valid_image(carrier_image, "Carrier image") - require_security_factors(pin, rsa_key_data) - - if pin: - require_valid_pin(pin) - if rsa_key_data: - require_valid_rsa_key(rsa_key_data, rsa_password) - - # Default date to today - if date_str is None: - date_str = date.today().isoformat() - - debug.print(f"Encoding for date: {date_str}") - - # Encrypt message/file - encrypted = encrypt_message( - message, reference_photo, day_phrase, date_str, pin, rsa_key_data - ) - - # Debug: show encrypted data size - debug.print(f"Encrypted payload: {len(encrypted)} bytes") - - # Get pixel key - pixel_key = derive_pixel_key( - reference_photo, day_phrase, date_str, pin, rsa_key_data - ) - - debug.data(pixel_key, "Pixel key") - - # Embed in image (returns extension too) - stego_data, stats, extension = embed_in_image( - carrier_image, encrypted, pixel_key, output_format=output_format - ) - - # Generate filename with correct extension - filename = generate_filename(date_str, extension=extension) - - debug.print(f"Encoding complete: {filename}, " - f"modified {stats.pixels_modified}/{stats.total_pixels} pixels " - f"({stats.modification_percent:.2f}%)") - - return EncodeResult( - stego_image=stego_data, - filename=filename, - pixels_modified=stats.pixels_modified, - total_pixels=stats.total_pixels, - capacity_used=stats.capacity_used, - date_used=date_str - ) - - -def encode_file( - filepath: Union[str, Path], - reference_photo: bytes, - carrier_image: bytes, - day_phrase: str, - pin: str = "", - rsa_key_data: Optional[bytes] = None, - rsa_password: Optional[str] = None, - date_str: Optional[str] = None, - output_format: Optional[str] = None, - filename_override: Optional[str] = None, -) -> EncodeResult: - """ - Encode a file into an image. - - Convenience function for embedding files. Preserves original filename. - - Args: - filepath: Path to file to embed - reference_photo: Shared reference photo bytes - carrier_image: Image to hide file in - day_phrase: Today's passphrase - pin: Static PIN (optional if using RSA key) - rsa_key_data: RSA private key PEM bytes (optional if using PIN) - rsa_password: Password for RSA key if encrypted - date_str: Date string YYYY-MM-DD (defaults to today) - output_format: Force output format ('PNG', 'BMP') - filename_override: Override the stored filename - - Returns: - EncodeResult with stego image and metadata - """ - debug.print(f"encode_file called: filepath={filepath}") - payload = FilePayload.from_file(str(filepath), filename_override) - - return encode( - message=payload, - reference_photo=reference_photo, - carrier_image=carrier_image, - day_phrase=day_phrase, - pin=pin, - rsa_key_data=rsa_key_data, - rsa_password=rsa_password, - date_str=date_str, - output_format=output_format, - ) - - -def encode_bytes( - data: bytes, - filename: str, - reference_photo: bytes, - carrier_image: bytes, - day_phrase: str, - pin: str = "", - rsa_key_data: Optional[bytes] = None, - rsa_password: Optional[str] = None, - date_str: Optional[str] = None, - output_format: Optional[str] = None, - mime_type: Optional[str] = None, -) -> EncodeResult: - """ - Encode raw bytes with a filename into an image. - - Convenience function for embedding binary data with metadata. - - Args: - data: Raw bytes to embed - filename: Filename to associate with the data - reference_photo: Shared reference photo bytes - carrier_image: Image to hide data in - day_phrase: Today's passphrase - pin: Static PIN (optional if using RSA key) - rsa_key_data: RSA private key PEM bytes (optional if using PIN) - rsa_password: Password for RSA key if encrypted - date_str: Date string YYYY-MM-DD (defaults to today) - output_format: Force output format ('PNG', 'BMP') - mime_type: MIME type of the data - - Returns: - EncodeResult with stego image and metadata - """ - debug.print(f"encode_bytes called: filename={filename}, data_size={len(data)}") - payload = FilePayload(data=data, filename=filename, mime_type=mime_type) - - return encode( - message=payload, - reference_photo=reference_photo, - carrier_image=carrier_image, - day_phrase=day_phrase, - pin=pin, - rsa_key_data=rsa_key_data, - rsa_password=rsa_password, - date_str=date_str, - output_format=output_format, - ) - - -@debug.time -def decode( - stego_image: bytes, - reference_photo: bytes, - day_phrase: str, - pin: str = "", - rsa_key_data: Optional[bytes] = None, - rsa_password: Optional[str] = None, - date_str: Optional[str] = None, -) -> DecodeResult: - """ - Decode a secret message or file from a stego image. - - High-level convenience function that handles extraction - and decryption in one call. - - Args: - stego_image: Image containing hidden message/file - reference_photo: Shared reference photo bytes - day_phrase: Passphrase for the day message was encoded - pin: Static PIN (if used during encoding) - rsa_key_data: RSA private key PEM bytes (if used during encoding) - rsa_password: Password for RSA key if encrypted - - Returns: - DecodeResult with: - - .payload_type: 'text' or 'file' - - .message: Decoded text (if text) - - .file_data: Decoded bytes (if file) - - .filename: Original filename (if file) - - .is_text / .is_file: Convenience properties - - Raises: - ValidationError: If inputs are invalid - SecurityFactorError: If no PIN or RSA key provided - ExtractionError: If data cannot be extracted - DecryptionError: If decryption fails - """ - debug.print(f"decode called: stego_image_size={len(stego_image)}, " - f"day_phrase='{day_phrase[:20]}...'") - - # Validate inputs - require_security_factors(pin, rsa_key_data) - - if pin: - require_valid_pin(pin) - if rsa_key_data: - require_valid_rsa_key(rsa_key_data, rsa_password) - - # Try to extract with today's date first - # Use provided date or fall back to today - if date_str is None: - date_str = date.today().isoformat() - pixel_key = derive_pixel_key( - reference_photo, day_phrase, date_str, pin, rsa_key_data - ) - - debug.data(pixel_key, "Pixel key for extraction") - - encrypted = extract_from_image(stego_image, pixel_key) - - # If we got data, check if it's from a different date - if encrypted: - header = parse_header(encrypted) - if header and header['date'] != date_str: - debug.print(f"Found different date in header: {header['date']} (expected {date_str})") - # Re-extract with correct date - pixel_key = derive_pixel_key( - reference_photo, day_phrase, header['date'], pin, rsa_key_data - ) - encrypted = extract_from_image(stego_image, pixel_key) - - if not encrypted: - debug.print("No data extracted from image") - raise ExtractionError("Could not extract data. Check your inputs.") - - debug.print(f"Extracted {len(encrypted)} bytes from image") - debug.data(encrypted[:64], "First 64 bytes of extracted data") - - # Decrypt and return full result - return decrypt_message(encrypted, reference_photo, day_phrase, pin, rsa_key_data) - - -def decode_text( - stego_image: bytes, - reference_photo: bytes, - day_phrase: str, - pin: str = "", - rsa_key_data: Optional[bytes] = None, - rsa_password: Optional[str] = None, - date_str: Optional[str] = None, -) -> str: - """ - Decode a text message from a stego image. - - Convenience function that returns just the text string. - Raises an error if the content is a binary file. - - Args: - stego_image: Image containing hidden message - reference_photo: Shared reference photo bytes - day_phrase: Passphrase for the day message was encoded - pin: Static PIN (if used during encoding) - rsa_key_data: RSA private key PEM bytes (if used during encoding) - rsa_password: Password for RSA key if encrypted - - Returns: - Decrypted message string - - Raises: - DecryptionError: If content is a binary file, not text - """ - debug.print("decode_text called") - result = decode(stego_image, reference_photo, day_phrase, pin, rsa_key_data, rsa_password) - - if result.is_file: - # Try to decode file as text - if result.file_data: - try: - return result.file_data.decode('utf-8') - except UnicodeDecodeError: - debug.print(f"File is binary: {result.filename or 'unnamed'}") - raise DecryptionError( - f"Content is a binary file ({result.filename or 'unnamed'}), not text. " - "Use decode() instead and check result.is_file." - ) - return "" - - debug.print(f"Decoded text: {result.message[:100] if result.message else 'empty'}...") - return result.message or "" - - -__all__ = [ - # Version - '__version__', - - # High-level API - 'encode', - 'encode_file', - 'encode_bytes', - 'decode', - 'decode_text', - 'generate_credentials', - - # Constants - 'DAY_NAMES', - 'LOSSLESS_FORMATS', - 'MAX_MESSAGE_SIZE', - 'MAX_FILE_PAYLOAD_SIZE', - - # Models - 'Credentials', - 'EncodeInput', - 'EncodeResult', - 'DecodeInput', - 'DecodeResult', - 'EmbedStats', - 'KeyInfo', - 'ValidationResult', - 'FilePayload', - - # Exceptions - 'StegasooError', - 'ValidationError', - 'PinValidationError', - 'MessageValidationError', - 'ImageValidationError', - 'KeyValidationError', - 'SecurityFactorError', - 'CryptoError', - 'EncryptionError', - 'DecryptionError', - 'KeyDerivationError', - 'KeyGenerationError', - 'KeyPasswordError', - 'SteganographyError', - 'CapacityError', - 'ExtractionError', - 'EmbeddingError', - 'InvalidHeaderError', - - # Key generation - 'generate_pin', - 'generate_phrase', - 'generate_day_phrases', - 'generate_rsa_key', - 'export_rsa_key_pem', - 'load_rsa_key', - 'get_key_info', - - # Validation - 'validate_pin', - 'validate_message', - 'validate_payload', - 'validate_file_payload', - 'validate_image', - 'validate_rsa_key', - 'validate_security_factors', - 'validate_phrase', - 'validate_date_string', - 'require_valid_pin', - 'require_valid_message', - 'require_valid_payload', - 'require_valid_image', - 'require_valid_rsa_key', - 'require_security_factors', - - # Crypto - 'encrypt_message', - 'decrypt_message', - 'decrypt_message_text', - 'derive_hybrid_key', - 'derive_pixel_key', - 'hash_photo', - 'parse_header', - 'get_date_from_encrypted', - 'has_argon2', - - # Steganography - 'embed_in_image', - 'extract_from_image', - 'calculate_capacity', - 'get_image_dimensions', - 'get_image_format', - 'is_lossless_format', - - # Utilities - 'generate_filename', - 'parse_date_from_filename', - 'get_day_from_date', - 'get_today_date', - 'get_today_day', - 'secure_delete', - 'SecureDeleter', - 'format_file_size', - - # Debugging - 'debug', -]