#!/usr/bin/env python3 """ Debug script for DCT/jpegio extraction issues. Run from the stegasoo directory. """ import sys import struct from pathlib import Path sys.path.insert(0, str(Path(__file__).parent / 'src')) import hashlib import numpy as np # Check for jpegio try: import jpegio as jio print("✓ jpegio available") except ImportError: print("✗ jpegio NOT available") sys.exit(1) def get_usable_positions(coef_array, min_magnitude=2): """Get positions of usable coefficients.""" positions = [] h, w = coef_array.shape for row in range(h): for col in range(w): # Skip DC coefficients (top-left of each 8x8 block) if (row % 8 == 0) and (col % 8 == 0): continue if abs(coef_array[row, col]) >= min_magnitude: positions.append((row, col)) return positions def generate_order(num_positions, seed): """Generate pseudo-random order for coefficient selection.""" hash_bytes = hashlib.sha256(seed + b"jpeg_coef_order").digest() rng = np.random.RandomState(int.from_bytes(hash_bytes[:4], 'big')) order = list(range(num_positions)) rng.shuffle(order) return order def extract_bits(coef_array, positions, order, num_bits): """Extract bits from coefficients.""" bits = [] for i, pos_idx in enumerate(order): if i >= num_bits: break row, col = positions[pos_idx] coef = coef_array[row, col] bits.append(coef & 1) return bits def bits_to_bytes(bits): """Convert list of bits to bytes.""" result = [] for i in range(0, len(bits), 8): byte_bits = bits[i:i+8] if len(byte_bits) == 8: byte_val = sum(byte_bits[j] << (7-j) for j in range(8)) result.append(byte_val) return bytes(result) def main(): if len(sys.argv) < 3: print("Usage: python debug_jpegio.py ") print("\nOptional: add passphrase, pin, key path") print(" python debug_jpegio.py stego.jpg ref.jpg 'passphrase' '123456' key.pem") sys.exit(1) stego_path = sys.argv[1] ref_path = sys.argv[2] passphrase = sys.argv[3] if len(sys.argv) > 3 else "test" pin = sys.argv[4] if len(sys.argv) > 4 else "" key_path = sys.argv[5] if len(sys.argv) > 5 else None print(f"\n{'='*60}") print("JPEGIO DCT EXTRACTION DEBUG") print(f"{'='*60}") print(f"Stego image: {stego_path}") print(f"Reference: {ref_path}") print(f"Passphrase: '{passphrase}'") print(f"PIN: '{pin}'") print(f"Key: {key_path}") # Load stego image with jpegio print(f"\n[1] Loading stego image with jpegio...") try: jpeg = jio.read(stego_path) print(f" ✓ jpegio.read() succeeded") print(f" Number of components: {len(jpeg.coef_arrays)}") for i, arr in enumerate(jpeg.coef_arrays): print(f" Component {i}: shape={arr.shape}, dtype={arr.dtype}") except Exception as e: print(f" ✗ Failed: {e}") sys.exit(1) # Get coefficient array (channel 0) coef_array = jpeg.coef_arrays[0] print(f"\n[2] Coefficient array analysis...") print(f" Shape: {coef_array.shape}") print(f" Non-zero coefficients: {np.count_nonzero(coef_array)}") print(f" Min value: {coef_array.min()}") print(f" Max value: {coef_array.max()}") # Get usable positions print(f"\n[3] Finding usable positions (|coef| >= 2, non-DC)...") positions = get_usable_positions(coef_array) print(f" Usable positions: {len(positions)}") print(f" Capacity: ~{len(positions) // 8} bytes") # Generate seed (this needs to match the encode seed!) print(f"\n[4] Generating seed...") # Load reference photo ref_data = Path(ref_path).read_bytes() ref_hash = hashlib.sha256(ref_data).digest() print(f" Reference hash: {ref_hash[:8].hex()}...") # Load RSA key if provided rsa_component = b"" if key_path: try: from stegasoo import load_rsa_key key_data = Path(key_path).read_bytes() # Try without password first try: rsa_key = load_rsa_key(key_data, password=None) except: rsa_key = load_rsa_key(key_data, password="testpass") # Get public key bytes for seed from cryptography.hazmat.primitives import serialization pub_bytes = rsa_key.public_key().public_bytes( encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo ) rsa_component = hashlib.sha256(pub_bytes).digest() print(f" RSA key loaded, hash: {rsa_component[:8].hex()}...") except Exception as e: print(f" ✗ Could not load RSA key: {e}") # Build seed like stegasoo does # This is the critical part - must match encoding! seed_parts = [ ref_hash, passphrase.encode('utf-8'), pin.encode('utf-8') if pin else b"", rsa_component, ] seed = hashlib.sha256(b"".join(seed_parts)).digest() print(f" Combined seed: {seed[:8].hex()}...") # Generate order print(f"\n[5] Generating coefficient order...") order = generate_order(len(positions), seed) print(f" First 10 indices: {order[:10]}") # Try to extract header print(f"\n[6] Extracting header (first 80 bits = 10 bytes)...") HEADER_SIZE = 10 header_bits = extract_bits(coef_array, positions, order, HEADER_SIZE * 8) header_bytes = bits_to_bytes(header_bits) print(f" Raw header bytes: {header_bytes.hex()}") print(f" As ASCII (if printable): {repr(header_bytes)}") # Check for JPGS magic JPEGIO_MAGIC = b'JPGS' if header_bytes[:4] == JPEGIO_MAGIC: print(f" ✓ Found JPEGIO magic bytes!") version = header_bytes[4] flags = header_bytes[5] data_length = struct.unpack('>I', header_bytes[6:10])[0] print(f" Version: {version}") print(f" Flags: {flags}") print(f" Data length: {data_length} bytes") if data_length > 0 and data_length < len(positions) // 8: print(f"\n[7] Extracting payload ({data_length} bytes)...") total_bits = (HEADER_SIZE + data_length) * 8 all_bits = extract_bits(coef_array, positions, order, total_bits) data_bits = all_bits[HEADER_SIZE * 8:] payload = bits_to_bytes(data_bits) print(f" Payload (first 64 bytes): {payload[:64].hex()}") print(f" This should be encrypted data starting with salt/IV") else: print(f" ✗ Invalid data length: {data_length}") else: print(f" ✗ No JPEGIO magic found") print(f" Expected: {JPEGIO_MAGIC.hex()} ('JPGS')") print(f" Got: {header_bytes[:4].hex()} ('{header_bytes[:4]}')") # Try alternate interpretations print(f"\n[7] Trying alternate header interpretations...") # Maybe it's scipy DCT format? DCT_MAGIC = b'DCTS' if header_bytes[:4] == DCT_MAGIC: print(f" Found SCIPY DCT magic - wrong extraction method!") else: # Show bit distribution print(f" First 32 extracted bits: {header_bits[:32]}") # Check if bits look random or patterned ones = sum(header_bits[:80]) print(f" Bit distribution: {ones}/80 ones ({100*ones/80:.1f}%)") print(f"\n{'='*60}") print("DEBUG COMPLETE") print(f"{'='*60}\n") if __name__ == '__main__': main()