Date: 2026-08-31 • Predecessor: v1.5-implementation (a44bfe2) • Status: PLANNING PHASE
1. External metadata: Restore requires external parameters (salt, region, schema_version) passed separately
→ Files not self-contained; easy to lose metadata
2. Boundary artifacts: JPEG block boundaries have slight lossy effects (acceptable but noted)
→ Could be eliminated with rigorous MCU alignment
3. No progressive JPEG support: Only baseline DCT handled
→ Limits use cases in progressive-heavy workflows
4. Float precision in scipy DCT: 1.2-byte accuracy is good, but not perfect
→ libjpeg could improve to sub-byte with native coefficient access
Approach: Store metadata in JPEG APP15 segment (user-defined application data)
Why APP15: Not used by most codecs; preserved during JPEG re-encoding
Content:
• schema_version (1 byte)
• salt (16 bytes)
• region (x, y, w, h as 4×4 bytes = 16 bytes)
• block_count (4 bytes)
• reserved (3 bytes for future use)
Total: ~50 bytes overhead per image
v1.5 (current): restore image.jpg output.jpg --passphrase PASS --region X,Y,W,H --salt HEX
v2 (proposed): restore image.jpg output.jpg --passphrase PASS
(metadata auto-extracted from APP15)
Task 1: Create metadata.py functions for APP15 encoding
• encode_app15(): Pack salt/region/version into binary blob
• decode_app15(): Extract blob from JPEG image
Task 2: Integrate with Pillow's JPEG handling
• Use PIL.Image.app[15] to read/write segments
• Test round-trip: embed → save → load → extract
Task 1: Update scramble_image() to embed metadata
• After saving scrambled JPEG, inject APP15 metadata
Task 2: Update restore_image() to extract metadata
• Check for APP15; if present, use it; else fall back to external params
Task 3: CLI help text updates
• Show both v1.5 (external params) and v2 (embedded) examples
Task 1: Evaluate libjpeg-turbo availability
• If available: Integrate for native DCT coefficient access
• If not: Use scipy but with improved clipping/rounding documentation
Task 2: MCU alignment research
• Understand JPEG MCU (Minimum Coded Unit) boundaries
• Align region permutation to MCU boundaries to eliminate edge artifacts
Task 3: Coefficient overflow handling
• Document how coefficients outside [-1024, 1023] are handled
• Add tests for edge cases (high-contrast regions)
Task 1: Detect progressive vs baseline in input
Task 2: Handle progressive scan structure
• Research PIL's progressive JPEG capabilities
• Determine if permutation works across scans
Task 3: Testing
• Generate progressive JPEG test cases
• Validate round-trip with progressive input
Task 1: Full integration tests
• v2 scramble → v2 restore (APP15 embedded)
• v2 backward compat: Restore v1 files with external params
Task 2: Sample generation
• Generate 3 v2 samples (self-contained files)
• Compare file sizes with v1.5 (should be ~50 bytes larger)
Task 3: Performance profiling
• Measure scramble/restore time (target: <2s for 4K image)
Task 1: Update README with v2 examples
Task 2: Create v2 design spec (ideas/jpeg-obscura/v2-design)
Task 3: Session lessons (ideas/jpeg-obscura/v2-session-lessons)
1. APP15 segment corruption: JPEG re-encoding might strip APP15
Mitigation: Test with multiple JPEG encoders; verify preservation
2. libjpeg-turbo integration: FFI complexity, version compatibility
Mitigation: Keep scipy as fallback; use ctypes if needed
3. MCU alignment complexity: Region snapping might not align to MCU boundaries
Mitigation: Document as edge case; accept small boundary artifacts for MVP
1. Progressive JPEG complexity: Scan structure handling is non-trivial
Mitigation: Accept baseline-only for v2.0; progressive in v2.1
2. Backward compatibility: Ensure v1.5 files still restore correctly
Mitigation: Comprehensive test suite for both formats
✓ APP15 encoding/decoding (pack/unpack binary blob)
✓ Metadata round-trip (embed → extract → verify)
✓ Backward compat: v1.5 files (no APP15) should restore with external params
✓ v2 scramble (APP15 embedded) → v2 restore (APP15 extracted)
✓ v2 restore → original accuracy (target: mean Δ < 1.5 bytes)
✓ v1.5 scramble → v2 restore (backward compat)
✓ File portability: v2 scrambled file to different machine, restore succeeds
✓ Side-by-side comparison: v1.5 vs v2 restoration (visual similarity)
✓ Progressive JPEG samples (if implemented)
✓ APP15 metadata embedding works (embed → JPEG save → JPEG load → extract)
✓ Backward compat with v1.5 (restore still works with external params)
✓ Restoration accuracy maintained (mean Δ < 1.5 bytes)
✓ CLI simplified: restore image.jpg output.jpg --passphrase PASS works
✓ Enhanced coefficient handling (libjpeg-turbo or scipy improvements)
✓ MCU alignment handling (document limitations if not fully solved)
✓ Performance profiling (scramble <2s for 4K)
✓ Progressive JPEG support
✓ Streaming API (large image support)
✓ Batch processing API
v0.5 scramble → v0.5 restore: ✓
v0.5 scramble → v1 restore: ✗ (different schema)
v1 scramble → v1 restore: ✓
v1 scramble → v1.5 restore: ✓ (backward compat)
v1.5 scramble → v2 restore: ✓ (backward compat with external params)
v2 scramble → v2 restore: ✓ (APP15 embedded)
v2 scramble → v1.5 restore: ✗ (no APP15 support in v1.5)
Risk: Region coordinates visible in APP15 segment
Mitigation: Metadata is not sensitive (region is visible from encrypted image anyway)
Salt is stored in plaintext in APP15 (necessary for KDF)
Risk: Attacker modifies APP15 to wrong region/salt
Mitigation: Wrong salt → wrong key derivation → garbage output (detected)
No authentication of APP15 needed (confidentiality is in permutation, not metadata)
Total: 3.5 days (0.5 + 0.5 + 1 + 1 + 1 + 0.5)
Assumes: libjpeg-turbo optional; progressive JPEG deferred; MCU alignment documented but not fixed
Slack: +0.5 days for testing/debugging
• libjpeg-turbo (optional, for enhanced coefficient access)
• PIL (already present)
APP15 handling via PIL
Coefficient handling via scipy (already present)
• GPU acceleration (DCT via cuFFT)
• Streaming API (process images larger than RAM)
• Batch processing (multiple images/regions)
• Parallel permutation (multi-threaded for large block counts)
• Selective obscuring (obscure multiple regions with different keys)
• Reveal-on-demand (progressive unobscuring)
• Time-locked obscuring (reveal only after timestamp)
v2 focuses on practical usability (self-contained files via APP15) and robustness (enhanced coefficient handling, backward compat). Estimated 3.5-day effort for MVP; full implementation depends on libjpeg-turbo availability and progressive JPEG requirements.