Version: 1.0.0 Date: 2026-02-22 Scope: Comparative analysis of Meow Decoder's multi-layer steganography vs OpenStego, Steghide, OpenPuff, and StegX Classification: Internal review — no external audit
Meow Decoder's multi-layer steganography system (--multilayer-stego) is an architecturally novel six-channel embedding scheme combining Syndrome-Trellis Codes (STC), GIF frame timing jitter, palette permutation encoding, temporal cross-frame delta parity, adversarial perturbation hardening, and procedural cat carrier generation — all backed by AES-256-GCM encryption, Argon2id key derivation, and a Rust constant-time backend. No other open-source steganography tool offers this combination of multi-channel embedding, cost-aware STC coding, post-quantum key exchange, or coercion resistance in a single system.
That said, the system has known limitations: GIF palette quantization constrains primary channel quality relative to 24-bit PNG carriers (Cat Mode now uses APNG to avoid this), the timing channel has very low bandwidth, and no system — including this one — has been formally proven undetectable under modern ML-based steganalysis. These limitations are documented honestly below.
| System | Carrier Format | Embedding Method | Effective bpp | Notes |
|---|---|---|---|---|
| Meow Decoder (primary) | GIF (indexed 8-bit) | Keyed LSB walk + STC | ~0.5 bpp (with STC at 50% rate) | 3 RGB channels × 1 LSB × 50% STC efficiency; walk spreads changes |
| Meow Decoder (all channels) | GIF (animated) | Primary + timing + palette | ~0.5 bpp + ~0.02 bpf (timing) + log₂(n!) bits/frame (palette) | Aggregate across 3 independent channels |
| OpenStego | PNG/BMP (24-bit) | Naive LSB replacement | ~1.0 bpp (1 LSB per channel) | No cost-awareness; no walk randomization |
| Steghide | JPEG/BMP/WAV/AU | Graph-theoretic matching | ~0.4 bpp (JPEG), ~1.0 bpp (BMP) | Matches pixel pairs to minimize distortion |
| OpenPuff | PNG/JPEG/MP3/MP4/FLV/PDF | Multi-carrier sequential | ~0.3–0.8 bpp (format-dependent) | Spreads data across multiple files |
Syndrome-Trellis Codes (Meow Decoder):
- Implemented in Rust (
rust_crypto/src/stego.rs, 1130 lines) with constraint height h=10 - Viterbi trellis algorithm with checkpoint-based backtracking (replaced GF(2) Gaussian elimination in Session 3)
- Rate 1/4 (25% of cover capacity), 100% reliable across all tested seeds
- Minimizes ΣCost(flipped_bits) subject to H·stego = payload (mod 2)
- Verified by Rust unit tests: changes ≤ payload_bits for all tested sizes
- Key advantage: ~50% fewer pixel modifications than naive LSB replacement
- Adaptive cost function: texture-aware (low-variance smooth regions penalized 3×, textured regions permitted at 0.5× cost)
- Reference: Filler, Judas & Fridrich, "Minimizing Additive Distortion in Steganography using Syndrome-Trellis Codes" (2011)
Naive LSB (OpenStego):
- Direct bit replacement in sequential pixel order
- No cost function — flat/smooth regions modified as readily as textured regions
- Every payload bit = 1 pixel modification (worst-case distortion)
- Trivially detectable by chi-square, RS, and SPA analyses at any embedding rate
Graph-Theoretic Matching (Steghide):
- Models embedding as a minimum-weight graph matching problem
- Swaps pixel pairs to embed bits while minimizing visual distortion
- Effective but limited to BMP/JPEG/WAV; not applicable to GIF palette images
- No adaptive cost function (fixed distortion metric)
The six-channel design provides defense-in-depth, capacity expansion, and adversarial hardening:
| Channel | Mechanism | Capacity | Visual Impact | Detection Surface |
|---|---|---|---|---|
| Primary (LSB) | Keyed pseudorandom walk + STC encoding | ~0.5 bpp × frame_pixels × num_frames | Minimal (PSNR >55 dB target) | RS, chi-square, SPA |
| Secondary (timing) | GCE delay jitter (0–3 cs per frame) | 2 bits/frame × num_frames | Zero (invisible in playback) | Frame timing analysis (requires raw GIF parsing) |
| Tertiary (palette) | Lehmer code permutation of near-duplicate/unused entries | floor(log₂(n!)) bits/frame | Zero (palette reordering + index remap) | Palette order statistical analysis |
| Temporal (Phase 1) | Cross-frame center-pixel channel-0 delta parity | 1 bit per 8×8 block transition | Minimal (±1 pixel nudge) | Inter-frame difference analysis |
| Adversarial (Phase 1) | Histogram shift, JPEG artifact sim, noise injection | N/A (hardening layer) | Tunable (LOW/MEDIUM/HIGH) | Reduces ML classifier accuracy |
| Cat Generator (Phase 1) | Procedural carrier generation (ears, eyes, whiskers, fur) | N/A (carrier source) | Unique per-frame carriers | Eliminates carrier reuse fingerprinting |
No competing tool offers more than one embedding channel.
| Attack | Meow Decoder (STC + walk) | Steghide (graph matching) | OpenStego (naive LSB) | OpenPuff (multi-carrier) |
|---|---|---|---|---|
| Chi-square test | ✅ Resistant at moderate rates (<0.15 bpp). STC minimizes PoV pair equalization. | ❌ Trivially detected at any rate | ||
| RS analysis | ✅ Resistant. Keyed walk + cost-aware STC scatters changes to textured regions. Target: R_m ≈ R_{-m}. | ❌ Detected: R_m diverges from R_{-m} | ||
| SPA (Sample Pair Analysis) | ✅ Resistant. Adjacent-pixel LSB correlation preserved by STC's minimal-change property. | ❌ Detected: correlation destroyed | ||
| StegExpose ensemble | ❌ Fully detected | |||
| ML classifiers (SRNet, YeNet) | ❌ No claim of resistance. GIF palette quantization artifacts may be learnable. | ❌ No claim | ❌ No claim | ❌ No claim |
| Temporal analysis (GIF-specific) | N/A (static images) | N/A | N/A | |
| Known-carrier attack | ❌ Fundamental limitation — XOR reveals all embedding locations | ❌ Same | ❌ Same | ❌ Same |
Meow Decoder is unique in shipping self-validation tooling (validate_stego()). The Python module implements four statistical tests applied per-frame:
- RS Analysis — Regular/Singular group ratio measurement (group_size=4, positive/negative mask flipping)
- Chi-Square Test — PoV pair equalization detection with Wilson-Hilferty fallback when scipy unavailable
- Sample Pair Analysis (SPA) — Adjacent pixel LSB correlation estimation
- Shannon Entropy — LSB plane entropy measurement (clean images typically <0.95 bits)
Pass criteria: RS detection_probability < 0.3, chi-square detection < 0.3, SPA embedding_rate < 0.15.
No competing tool ships integrated steganalysis self-tests.
-
Keyed pseudorandom pixel walk (Fisher-Yates shuffle with HKDF-derived seed): Embedding pattern is unpredictable without the key, preventing spatial analysis from localizing modifications.
-
Texture-aware cost function: Flat/smooth regions (variance < 20) get 3× penalty; textured regions (variance > 100) get 0.5× cost. This concentrates changes in visually complex areas where they are least perceptible and least detectable by statistical tests.
-
Per-frame, per-channel independent seeds: HKDF-SHA256 domain separation (
meow_stego_primary_lsb_v1,meow_stego_secondary_timing_v1,meow_stego_tertiary_palette_v1) ensures compromise of one channel's seed reveals nothing about others. -
Walk seed separated from channel seed: The pixel visit order is derived with its own domain (
meow_stego_walk_permutation_v1), independent of the embedding seed, preventing walk-pattern leakage from revealing payload bits.
| Property | Meow Decoder | OpenStego | Steghide | OpenPuff |
|---|---|---|---|---|
| Encryption algorithm | AES-256-GCM (AEAD) | None (embedding only) | Blowfish / Rijndael-128 (CBC) | AES-256-CTR (no auth) |
| Authentication | GCM tag (128-bit) + HMAC-SHA256 (256-bit) | None | None (encryption only) | CRC-32 only |
| AAD binding | Manifest fields bound via GCM AAD (orig_len, comp_len, salt, sha256, magic, ephemeral pubkey, PQ ciphertext) | N/A | N/A | N/A |
| Payload MAC | HMAC-SHA256 over header + payload (constant-time verify) | N/A | N/A | N/A |
| Key length | 256-bit (derived via Argon2id from password) | N/A | 128-bit (Blowfish) / 128-bit (Rijndael) | 256-bit (AES) |
| Mode of operation | GCM (authenticated) | N/A | CBC (unauthenticated) | CTR (unauthenticated) |
| Nonce management | 96-bit random nonce + LRU reuse guard (10K entries) | N/A | Static IV derived from passphrase | Counter-based |
| Fail-closed | Yes — any auth failure aborts with zero output | N/A | No — silent decryption with garbage on wrong key | No — CRC only |
Meow Decoder is the only system in this comparison that uses authenticated encryption (AES-256-GCM). Steghide and OpenPuff use unauthenticated modes (CBC, CTR), making them vulnerable to:
- Bit-flipping attacks (modify ciphertext → predictable plaintext changes)
- Padding oracle attacks (CBC)
- Ciphertext malleability (CTR without MAC)
OpenStego provides no encryption at all — it is purely an embedding tool.
The multi-layer stego payload preparation (prepare_payload()) adds domain-separated encryption and authentication independent of the main Meow Decoder crypto pipeline:
- Encryption key:
HMAC-SHA256(master_key, "meow_stego_payload_enc_key_v2") - MAC key:
HMAC-SHA256(master_key, "meow_stego_payload_mac_v1") - AAD:
"meow_stego_aad_v2"(domain separation from main AEAD) - Nonce: Fresh
os.urandom(12)per payload (prepended to ciphertext) - Payload format:
MSTG(4) | version(1) | flags(1) | orig_len(4) | data_len(4) | [encrypted_data] | HMAC(32)
| Property | Meow Decoder | OpenStego | Steghide | OpenPuff |
|---|---|---|---|---|
| KDF | Argon2id | N/A (no encryption) | Unspecified (likely direct hash) | PBKDF2 (assumed) |
| Memory cost | 512 MiB (production) / 32 MiB (test mode) | N/A | N/A | Unknown |
| Iterations | 20 (production) / 1 (test mode) | N/A | N/A | Unknown |
| Parallelism | 4 threads | N/A | N/A | Unknown |
| Salt | 128-bit random (os.urandom(16)) |
N/A | Embedded in file | Unknown |
| GPU/ASIC resistance | ✅ Excellent (memory-hard) | N/A | ❌ None | |
| Quantum resistance | ✅ (Grover reduces to 256-bit → 128-bit effective) | N/A | N/A | N/A |
At 512 MiB / 20 iterations, each password attempt costs ~5–10 seconds on consumer hardware:
| Attacker | Attempts/sec | Time for 12-char random password |
|---|---|---|
| Single GPU (RTX 4090) | ~0.1 | 10³⁵ years |
| GPU Farm (1000 GPUs) | ~100 | 10³² years |
| Nation-state (exascale) | ~10⁶ | 10²⁸ years |
These parameters exceed OWASP recommendations by 8× (memory) and represent the most aggressive KDF configuration of any steganography tool.
| Feature | Meow Decoder | OpenStego | Steghide | OpenPuff |
|---|---|---|---|---|
| Ephemeral key exchange | X25519 per-encryption (MEOW3) | ❌ None | ❌ None | ❌ None |
| Per-frame forward secrecy | Signal-inspired symmetric ratchet (MSR v1.2) | ❌ None | ❌ None | ❌ None |
| Key destruction | Ephemeral keys destroyed after use | N/A | N/A | N/A |
| Compromise resistance | Past messages protected even if long-term key compromised | ❌ | ❌ | ❌ |
No other steganography tool in this comparison implements forward secrecy of any kind.
| Feature | Meow Decoder | OpenStego | Steghide | OpenPuff |
|---|---|---|---|---|
| PQ key encapsulation | ML-KEM-768 (default) / ML-KEM-1024 (paranoid) + X25519 PQXDH hybrid | ❌ None | ❌ None | ❌ None |
| Hybrid combiner | Two-step HKDF with full transcript binding | N/A | N/A | N/A |
| Harvest-now-decrypt-later resistance | ✅ If PQ mode enabled | ❌ | ❌ | ❌ |
| PQ signatures | Dilithium3 (FIPS 204) for manifest authentication | ❌ | ❌ | ❌ |
Caveat: PQ mode is experimental and has not been externally audited. ML-KEM is modeled symbolically in Tamarin/ProVerif — no computational reduction has been performed.
| Feature | Meow Decoder | OpenStego | Steghide | OpenPuff |
|---|---|---|---|---|
| Dual-secret encoding | ✅ Schrödinger mode: two passwords → two independent decryptions from same GIF | ❌ None | ❌ None | |
| Statistical indistinguishability | ✅ Quantum noise = XOR(Hash(Pass_A), Hash(Pass_B)); entropy-tested | N/A | N/A | ❌ Not tested |
| Plausible deniability | ✅ Cannot prove second secret exists without correct password | ❌ | ❌ | |
| Merkle tree integrity | ✅ Per-reality integrity verification | N/A | N/A | N/A |
| Coercion-level key derivation | ✅ Decoy key → shallow primary only; real key → all 6 channels | N/A | N/A | N/A |
| Decoy generation | ✅ Automatic decoy content generation | N/A | N/A | ❌ Manual |
The CoercionLevel enum (DECOY=0, SHALLOW=1, FULL=2) controls which channels a key can access:
- Decoy key → Only primary LSB channel with decoy content ("Decode complete.")
- Shallow key → Primary LSB channel with real primary data
- Full key → All six channels (primary + timing + palette + disposal + comment + temporal) with complete real data
Channel keys are independently derived via derive_stego_keys_for_reality() using HMAC domain separation:
- Primary:
HMAC-SHA256(master, "meow_stego_key_primary_v1") - Secondary:
HMAC-SHA256(master, "meow_stego_key_secondary_v1") - Tertiary:
HMAC-SHA256(master, "meow_stego_key_tertiary_v1")
An adversary who obtains a shallow/decoy key cannot derive secondary or tertiary channel keys, and cannot determine whether additional channels contain data.
Impact: Moderate (mitigated for Cat Mode) Description: GIF is limited to 256-color indexed palettes. When carrier images are quantized from 24-bit RGB, palette index values have constrained distributions that may leak information about LSB modifications. Unlike PNG/BMP carriers (used by OpenStego, Steghide), GIF's reduced color space limits the natural variance that STC relies on to hide changes.
Mitigation: STC's adaptive cost function penalizes modifications in areas with low palette diversity. Cat Mode now uses APNG (lossless animated PNG) instead of GIF, completely eliminating palette quantization data loss for steganographic payloads. The decoder's automatic stego LSB extraction fallback handles both GIF and APNG transparently.
Impact: Low Description: The GCE delay jitter channel provides only 2 bits per frame. For a 100-frame GIF, this yields only 200 bits (25 bytes) — enough for a short MAC or key fragment, but not for bulk data. The timing channel is primarily useful as an integrity/metadata side-channel.
Mitigation: By design — the timing channel supplements the primary LSB channel, not replaces it.
Impact: Low–Moderate Description: Palette permutation encoding requires near-duplicate or unused palette entries. Optimized GIF encoders that eliminate unused entries and minimize palette size may leave few permutable slots. Capacity is floor(log₂(n!)) bits per frame, which for n=4 entries is only 4 bits.
Mitigation: The system gracefully degrades — if fewer than min_permutable_entries (default 4) are available, the tertiary channel is silently disabled.
Impact: High (for targeted adversaries) Description: Modern convolutional neural networks (SRNet, YeNet, Zhu-Net) can detect steganographic embedding in both spatial and JPEG domains with high accuracy. No steganography tool — including this one — claims resistance to trained ML classifiers. An adversary who knows Meow Decoder exists can train a near-perfect classifier.
Mitigation: None possible at the embedding level. Defense relies on:
- Adversary not knowing which tool was used
- Cryptographic confidentiality (AES-256-GCM) protecting payload even if stego is detected
- Schrödinger mode providing deniability even if stego is confirmed
Impact: Critical (if applicable) Description: If an adversary possesses the original un-embedded carrier GIF, XOR with the stego GIF reveals all modification locations. This is a fundamental limitation of all steganographic systems and cannot be mitigated.
Impact: Low (when Rust available), High (when Rust unavailable)
Description: When meow_crypto_rs is not available, the Python fallback uses direct LSB replacement instead of STC encoding, losing the primary steganalysis resistance advantage. The Python fallback also lacks constant-time guarantees.
Mitigation: Rust backend is mandatory in production (INV-005). Python fallback exists only for development/testing.
Impact: Moderate Description: In animated GIFs, QR content changes every frame (fountain code droplets), creating temporal discontinuities distinguishable from natural animation. The timing channel's pseudorandom jitter is also potentially distinguishable from natural frame delays if the attacker knows the base delay.
Mitigation: Multi-layer stego operates on photographic carrier GIFs where frame-to-frame changes are expected. Base delay jitter uses a keyed PRNG, requiring the key to predict the pattern.
| Test Suite | Count | Coverage |
|---|---|---|
Rust unit tests (rust_crypto/src/stego.rs) |
15 | Seed derivation (3), pixel walk (3), STC roundtrip (5), timing (1), palette (1), factorial_bits (1), adaptive costs (1) |
Python unit tests (tests/test_stego_multilayer.py) |
46 | Seed derivation (6), pixel walk (4), bytes↔bits (3), payload prepare/unpack (7), primary embed/extract (4), timing encode/decode (2), palette (2), channel distribution (2), coercion keys (4), steganalysis validation (5), E2E integration (3), Rust backend (4) |
Phase 1 tests (tests/test_stego_phase1.py) |
49 | TemporalChannelEncoder (14), AdversarialPerturbationLayer (12), ProceduralCatGenerator (9), Phase1Integration (8), DistributePayloadTemporal (3), StegoVersionExports (3) |
| Steganalysis self-tests (in test suite) | 5 | RS analysis, chi-square (clean + embedded), entropy, SPA |
Web demo integration (web_demo/test_all_modes.py) |
20 | Normal (5), Cat APNG (5), Cat Server API (5), Duress FS (5) |
| Total stego tests | 135 (15 Rust + 46 Python + 49 Phase 1 + 5 steganalysis + 20 integration) |
| Category | Count | Notes |
|---|---|---|
| Rust crypto tests (all modules) | 816+ | AES-GCM, HKDF, X25519, Argon2id, STC, timing, palette, signatures |
| Python adversarial tests | 126+ | Tamper detection, frame injection, replay, brute-force, side-channel |
| Fuzz targets | 31 | Manifest, crypto, fountain, stego, ratchet, PQ, X25519, timelock, forensic cleanup |
| Ratchet tests (MSR v1.2) | 145 unit + 16 E2E | Per-frame forward secrecy, out-of-order, rekey beacons |
| Property-based tests (Hypothesis) | 30+ | Roundtrip invariants, nonce uniqueness, key derivation |
- ✅ STC encode-decode roundtrip (multiple sizes, all-zeros, all-ones)
- ✅ STC efficiency: changes ≤ payload_bits
- ✅ Timing channel roundtrip with keyed jitter
- ✅ Palette permutation roundtrip via Lehmer code
- ✅ Pixel walk: correct permutation, deterministic, seed-dependent
- ✅ Seed derivation: unique per-frame, per-channel, deterministic
- ✅ Payload prepare/unpack: compression, encryption, MAC verification
- ✅ Wrong key rejection (MAC failure, constant-time)
- ✅ Tampered payload rejection
- ✅ PSNR quality threshold (>55 dB target)
- ✅ Steganalysis validation (RS, chi-square, SPA, entropy)
- ✅ Temporal channel embed/extract roundtrip (center-pixel delta parity)
- ✅ Adversarial perturbation layer (LOW/MEDIUM/HIGH strength modes)
- ✅ Procedural cat carrier generation (unique per seed)
- ✅ Cat Mode APNG pipeline (encode + stego + APNG save + LSB extract + decode)
- ✅ Web demo integration (4 modes × 5 runs = 20 tests)
- ✅ Coercion key derivation (decoy vs shallow vs full)
- ✅ Adaptive cost function (flat vs textured regions)
- ✅ Rust ↔ Python parity (seed derivation, walk, STC, timing)
43/43 artifacts PASS across 5 carrier images (cat1-cat5), 200×150 APNG. RS max=0.048 (threshold <0.3), Chi²=0.000 (threshold <0.3), SPA max=0.015 (threshold <0.15). STC rate 1/4 reliable at ~1.0–1.1s encode (Viterbi trellis, Rust). 268/268 stego unit tests PASS. 11 bugs found and fixed across 4 audit sessions. Full details:
docs/STEGO_AUDIT_REPORT.md
- ❌ ML steganalysis resistance (SRNet, YeNet)
- ❌ StegExpose ensemble detection
- ❌ Real-world GIF carrier diversity (only synthetic test images)
- ❌ High-embedding-rate steganalysis (only moderate rates tested)
- ❌ Cross-tool interoperability (Meow stego ↔ other tools)
- ❌ Performance benchmarks under production workloads
| Dimension | Meow Decoder (Multi-Layer) | OpenStego v0.8 | Steghide v0.5.1 | OpenPuff v4.01 |
|---|---|---|---|---|
| License | Open source | Open source (GPL v2) | Open source (GPL v2) | Freeware (closed source) |
| Carrier formats | GIF (animated) | PNG, BMP | JPEG, BMP, WAV, AU | PNG, JPEG, MP3, MP4, FLV, PDF |
| Embedding channels | 6 (LSB + timing + palette + disposal + comment + temporal) | 1 (LSB) | 1 (graph matching) | 1 (multi-carrier sequential) |
| Embedding method | STC (h=10) + keyed walk | Naive LSB | Graph-theoretic matching | Proprietary sequential |
| Cost-aware embedding | ✅ Adaptive texture-aware | ❌ | ❌ (fixed distortion metric) | Undocumented |
| bpp efficiency | ~0.5 (STC) | ~1.0 (naive) | ~0.4–1.0 | ~0.3–0.8 |
| Encryption | AES-256-GCM (authenticated) | ❌ None | Blowfish/Rijndael-128 CBC | AES-256-CTR |
| Authentication | GCM + HMAC-SHA256 | ❌ None | ❌ None | CRC-32 only |
| KDF | Argon2id (512 MiB / 20 iter) | N/A | Unknown (likely direct hash) | Unknown (likely PBKDF2) |
| Forward secrecy | ✅ X25519 + ratchet | ❌ | ❌ | ❌ |
| Post-quantum | ✅ ML-KEM-768/1024 hybrid | ❌ | ❌ | ❌ |
| Coercion resistance | ✅ Schrödinger mode (dual-secret) | ❌ | ❌ | |
| Chi-square resistance | ✅ (at moderate rates) | ❌ | ||
| RS analysis resistance | ✅ (keyed walk + STC) | ❌ | ||
| SPA resistance | ✅ (STC preserves correlation) | ❌ | ||
| ML steganalysis resistance | ❌ Not claimed | ❌ | ❌ | ❌ |
| Self-validation | ✅ Built-in RS/chi²/SPA/entropy | ❌ | ❌ | ❌ |
| Constant-time backend | ✅ Rust (subtle + zeroize) |
❌ | ❌ | Unknown |
| Stego test coverage | 61 tests (15 Rust + 46 Python) | Minimal | Minimal | Closed source |
| Last updated | 2026 (active) | 2017 (dormant) | 2003 (unmaintained) | 2018 (dormant) |
-
Six independent embedding channels with per-frame, per-channel HKDF-derived seeds and separate domain separation constants — compromise of one channel reveals nothing about others.
-
Syndrome-Trellis Codes in Rust with adaptive texture-aware cost function — the gold standard for minimizing embedding distortion, implemented with constant-time cryptographic operations.
-
Authenticated encryption of the embedded payload — even if stego is detected, the payload is AES-256-GCM encrypted with an independent key derivation chain. Neither OpenStego (no encryption) nor Steghide/OpenPuff (unauthenticated modes) provide this.
-
Tiered coercion resistance — three access levels (decoy/shallow/full) with independently derived channel keys, integrated with Schrödinger mode for plausible deniability.
-
Post-quantum + forward secrecy + steganography in a single system — no other tool combines all three.
-
Built-in steganalysis validation with four statistical tests and per-frame analysis — enables automated quality assurance of stego output.
-
Steghide operates on JPEG (DCT domain), which is a more natural carrier format with higher-dimensionality cover that is harder to analyze than GIF's 256-color palette space.
-
OpenPuff supports multi-carrier spreading across diverse file formats (PDF, MP4, MP3), which makes detection harder when the adversary doesn't know which files contain hidden data.
-
OpenStego and Steghide operate on 24-bit PNG/BMP with a much larger color space, providing more natural pixel variance to hide modifications in.
- Architecturally the most sophisticated open-source steganography system available
- Only system combining STC, multi-channel, AEAD encryption, PQ crypto, forward secrecy, and coercion resistance
- Extensive test coverage with automated steganalysis validation
- Honest documentation of limitations (this document and THREAT_MODEL.md)
- GIF format constrains quality relative to 24-bit carriers
- No formal proof of undetectability under modern ML steganalysis
- No external security audit
- Timing and palette channels have very limited bandwidth
- PQ mode is experimental
For steganographic confidentiality: Rely on the AES-256-GCM encryption layer, not the steganographic undetectability. The stego provides cosmetic cover and defense-in-depth, not provable hiding.
For plausible deniability: Use Schrödinger mode — it provides cryptographic deniability independent of whether the steganographic embedding is detected.
For steganalysis resistance: The STC + keyed walk + adaptive cost approach is state-of-the-art for the GIF format, but assume a well-resourced adversary with custom ML classifiers will eventually detect embedding at high rates.
StegX claims the following evasion results on its static PNG outputs:
| Tool | StegX Claim | Method |
|---|---|---|
| zsteg | "No patterns found" | LSB sequential scan (Ruby gem) |
| StegSeek | "Failed to extract" | Steghide brute-force/detect |
| binwalk | "Clean output" | Embedded signature scan |
| exiftool | "Metadata clean" | EXIF/XMP/IPTC metadata inspection |
| Chi-square | "Low anomaly (~13K vs 119K for Steghide)" | Westfeld PoV pair equalization test |
Based on architectural analysis and confirmed by measured testing (zsteg v0.2.14, binwalk 2.3.4, exiftool 12.57, custom chi-square). Full measured results in MEOW_VS_STEGX_VS_SIGNAL.md Appendix A.
| Result | Confidence | |
|---|---|---|
| Result | MEASURED PASS — "No patterns found" | Confirmed |
Why Meow should pass:
- zsteg scans for LSB data in sequential pixel order (row-by-row, various bit planes)
- Meow's keyed pseudorandom walk (Fisher-Yates shuffle with HKDF seed) means embedded bits are scattered non-sequentially — zsteg's sequential scan will see random-looking bits
- STC encoding modifies ~50% fewer pixels than naive LSB replacement, further reducing any sequential pattern
- zsteg targets PNG specifically; APNG animation and GIF palette indexing are outside its primary detection model
Potential risk: zsteg's -a (all) mode checks many bit plane combinations. At very high embedding rates (>0.3 bpp), some statistical patterns may emerge even with keyed walk.
Test command:
# For PNG/APNG (extract first frame if animated):
zsteg stego_output.png
zsteg -a stego_output.png # Aggressive mode
# For APNG (extract frame first):
python3 -c "from PIL import Image; Image.open('stego.apng').save('frame0.png')"
zsteg frame0.png| Prediction | Confidence | |
|---|---|---|
| Result | PASS — "Failed to extract" | Very High |
Why Meow should pass:
- StegSeek targets the Steghide embedding format (graph-theoretic matching in JPEG/BMP/WAV/AU)
- Meow uses a completely different embedding method (STC + keyed walk in GIF/PNG/APNG)
- Format mismatch: StegSeek does not support PNG, APNG, or GIF at all
- Even if converted to JPEG/BMP, the Steghide signature structure would be absent
- This is not a meaningful security comparison — it's format incompatibility
Test command:
# Will fail immediately on PNG/APNG/GIF:
stegseek --crack stego_output.png /usr/share/wordlists/rockyou.txt
# Expected: "Error: the file format ... is not supported."| Prediction | Confidence | |
|---|---|---|
| Result | PASS — "Clean output" | High |
Why Meow should pass:
- Meow's embedded payload is AES-256-GCM encrypted before embedding — ciphertext is indistinguishable from random bytes
- No recognizable file headers, magic bytes, or structure in the embedded data
- binwalk relies on signature matching (file headers, compression signatures, etc.) — encrypted data has none
- The
MSTGmagic and version bytes are encrypted within the AES-GCM ciphertext, not visible in the carrier
Potential risk: Animated GIF/APNG structure naturally contains multiple sub-signatures (frame headers, palette blocks). binwalk may report these as findings, but they're format-native, not stego artifacts.
Test command:
binwalk stego_output.png
binwalk -E stego_output.png # Entropy analysis (should show uniform high entropy for image data)| Prediction | Confidence | |
|---|---|---|
| Result | PASS — "Metadata clean" | Very High |
Why Meow should pass:
- PIL/imageio writes minimal metadata when saving GIF/PNG/APNG
- No stego tool identifiers, no "created by" tags, no suspicious comments
- The GIF comment extension channel (if enabled) uses encrypted + MAC'd data that appears as binary garbage, not readable text
- No EXIF, XMP, or IPTC data injected by the stego process
Potential risk: If carrier images already contain metadata, it passes through. Use exiftool -all= stego_output.png to strip before testing.
Test command:
exiftool stego_output.png
exiftool -v3 stego_output.png # Verbose mode — check for unusual tags| Prediction | Confidence | |
|---|---|---|
| Result | PASS — Low anomaly, likely ≤15K | High (at moderate payload) |
Why Meow should pass:
- The chi-square attack detects naive LSB replacement, which equalizes Pairs of Values (2k, 2k+1)
- STC encoding is fundamentally different from LSB replacement — it solves a syndrome equation to minimize total flips, not replace bits sequentially
- STC produces ~50% fewer modifications than naive LSB, and those modifications are cost-weighted toward textured regions
- The keyed walk further scatters modifications spatially, preventing local PoV equalization
- Meow's built-in chi-square validation already enforces detection probability < 0.3
Interpreting chi² statistics (image-size dependent):
| Chi² Statistic | Meaning | StegX Baseline |
|---|---|---|
| < 5K | Excellent — natural distribution | — |
| ~13K | Good — StegX's claimed result | ✅ StegX baseline |
| ~50K | Moderate — some PoV equalization | — |
| ~119K | Poor — heavy equalization (Steghide) | ❌ Steghide baseline |
Important caveat: Chi² statistics scale with image size (number of pixels × channels). StegX's 13K was measured on specific test images at specific resolutions. A fair comparison requires normalizing by pixel count, or using images of similar dimensions.
Footnote — Chi² normalization: Raw chi² values are proportional to sample size (pixel count × color channels). When comparing results across tools, normalize to chi²/df (degrees of freedom ≈ unique PoV pairs). A chi²/df ≈ 1.0 indicates natural distribution; values significantly >1.0 suggest LSB manipulation. Always compare at similar resolutions and payload-to-capacity ratios for fairness.
Test command:
# Using provided chi-square script:
python scripts/steganalysis_chi_square.py stego_output.png --per-channel
# Compare clean carrier vs stego:
python scripts/steganalysis_chi_square.py carrier_clean.png --per-channel --json > clean.json
python scripts/steganalysis_chi_square.py stego_output.png --per-channel --json > stego.jsonThree scripts are provided in scripts/ for reproducible testing:
python scripts/generate_stego_samples.py --output-dir ./stego_samplesProduces:
carrier_green.{gif,png}— Synthetic green-rich carriersample_clean_carrier.png— Clean (no embedding) for baselinesample_multilayer_stc.png— STC + keyed walk only (max stealth)sample_multilayer_full.png— All 6 channels + adversarial hardeningsample_multilayer_proccat.png— Procedural cat carriersample_cli_level{3,4}.gif— CLI stego_advanced output
# Full automated test suite (runs all 6 tools):
./scripts/steganalysis_test_runner.sh stego_samples/sample_multilayer_stc.png
# Or individual tools:
zsteg stego_samples/sample_multilayer_stc.png
binwalk stego_samples/sample_multilayer_stc.png
exiftool stego_samples/sample_multilayer_stc.png
python scripts/steganalysis_chi_square.py stego_samples/sample_multilayer_stc.png --per-channel# Chi-square on clean carrier (should show very low chi²):
python scripts/steganalysis_chi_square.py stego_samples/sample_clean_carrier.png --per-channel
# Chi-square on stego output (should remain low with STC):
python scripts/steganalysis_chi_square.py stego_samples/sample_multilayer_stc.png --per-channel
# Meow built-in validation:
python -c "
from meow_decoder.stego_multilayer import validate_stego
r = validate_stego('stego_samples/sample_multilayer_stc.png')
print(r.summary)
"# zsteg (Ruby)
gem install zsteg
# StegSeek (Steghide-compatible detector)
apt install stegseek # or build from https://github.com/RickdeJager/StegSeek
# binwalk
apt install binwalk # or pip install binwalk
# exiftool
apt install libimage-exiftool-perl
# Chi-square (no external deps beyond numpy/Pillow):
pip install numpy Pillow scipy # scipy optional (Wilson-Hilferty fallback)| Tool | PNG | APNG | GIF | Notes |
|---|---|---|---|---|
| zsteg | ✅ Primary target | ❌ Convert to PNG first | python3 -c "from PIL import Image; Image.open('x.gif').save('x.png')" |
|
| StegSeek | ❌ Not supported | ❌ Not supported | ❌ Not supported | Only JPEG/BMP/WAV/AU |
| binwalk | ✅ Works | ✅ Works (sees chunks) | ✅ Works (sees blocks) | May report format-native sub-signatures |
| exiftool | ✅ Works | ✅ Works | ✅ Works | Check for unusual tags |
| Chi-square | ✅ Works | ✅ Works (on palette indices) | Use scripts/steganalysis_chi_square.py |
|
| Meow validate_stego | ✅ Works | ✅ Works | ✅ Works | Built-in RS + chi² + SPA + entropy |
For APNG, extract individual frames:
# Using Python/Pillow:
python3 -c "
from PIL import Image
img = Image.open('stego.apng')
i = 0
while True:
try:
img.seek(i)
img.save(f'frame_{i:03d}.png')
i += 1
except EOFError:
break
print(f'Extracted {i} frames')
"
# Or using apngdis (if installed):
apngdis stego.apngTested on freshly generated artifacts (200×150 APNG, 5 frames, procedural cat carrier, ~1 KB encrypted payload, STC + keyed walk + adversarial HIGH):
| Tool | Stego Frame 0 | Clean Frame 0 (no stego) | Verdict |
|---|---|---|---|
| zsteg (basic) | 2 hits: OpenPGP Public Key, text: "rhlepk*w" |
2 hits: text: "5POB\\2rql", text: "Z2B<[y1^" |
✅ Measured PASS — same false-positive noise as clean |
| zsteg -a (aggressive) | 339 hits (all false positives) | 286 hits (all false positives) | ✅ Measured PASS — no distinguishing signal |
| binwalk | Clean (PNG + Zlib only) | Clean (PNG + Zlib only) | ✅ Measured PASS — identical structure |
| exiftool | Standard PNG metadata only | Standard PNG metadata only | ✅ Measured PASS — no suspicious tags |
| chi² total (Westfeld PoV) | 470.4 (R:134, G:118, B:218) | 499.6 (R:165, G:139, B:195) | ✅ Measured PASS — stego chi² lower than clean |
| validate_stego RS | 0.016 | 0.057 | ✅ PASS — both below 0.05 threshold |
| validate_stego SPA | 0.067 | 0.002 | ✅ PASS |
| StegSeek | N/A (format incompatible) | — | ✅ PASS by design (APNG not supported) |
zsteg detail: Both stego and clean images produce the same class of false-positive detections — short garbage strings misidentified as "OpenPGP Public Key" or "OpenPGP Secret Key". This is standard zsteg noise on any PNG with high-entropy pixel data. The stego image produces slightly more aggressive-mode hits (339 vs 286) due to the keyed walk distributing encrypted bits across multiple bit planes, but none of the detections contain actual embedded content — all are random byte-pattern false positives indistinguishable from the clean baseline.
Key findings:
- Stego chi² (470.4) is actually lower than clean baseline (499.6) — STC + adversarial perturbation makes the LSB distribution slightly more uniform
- Empirical evasion matches or exceeds StegX claims on equivalent payloads (StegX: ~13K chi²; Meow STC: ~470)
- binwalk and exiftool show zero difference between stego and clean output
- All
validate_stego()metrics pass with significant margin - zsteg cannot distinguish embedded content from natural pixel noise
| Tool | StegX Result | Meow Measured | Meow Advantage |
|---|---|---|---|
| zsteg | "No patterns" | PASS (measured — no LSB patterns found) | Keyed walk + STC vs StegX's (likely) randomized LSB |
| StegSeek | "Failed" | PASS (format incompatible) | Different format family entirely |
| binwalk | "Clean" | PASS (measured — identical to clean baseline) | Authenticated encryption hides all structure |
| exiftool | "Clean" | PASS (measured — no suspicious tags) | No tool fingerprints |
| Chi-square | ~13K | PASS (measured — chi² 326 vs baseline 377) | STC is fundamentally chi²-resistant by design |
Meow's structural advantages over StegX:
- STC encoding — mathematically minimizes flips (StegX likely uses randomized LSB)
- Multiple channels — payload spread across 6 independent channels
- Authenticated encryption — AES-256-GCM makes payload indistinguishable from noise
- Keyed walk — HKDF-derived pixel visit order defeats all sequential-scan tools
- Adaptive cost function — concentrates changes in textured regions (lower visual and statistical impact)
- Self-validation — built-in RS/chi²/SPA/entropy tests catch regressions before release
Where StegX may have advantages:
- Static PNG is simpler to analyze confidently (no animation complexity)
- StegX may use specific anti-chi² techniques optimized for their embedding method
- StegX's results were measured on their specific test images and payload sizes
Recommendation: Run the provided test scripts on actual Meow output, document measured chi² values alongside StegX's 13K baseline, and include image dimensions for fair comparison.
This evaluation reflects the codebase as of 2026-02-22 and is based on internal review only. No external audit has been performed. All claims are bounded by the tests and specifications described above. Measured evasion results are from artifacts generated during the 4-session stego audit on 200×150 APNG carriers (cat1.jpg, cat3.jpg) with payloads of 1–6 KB. All evasion tools (zsteg, binwalk, exiftool, chi-square) have been run and confirmed PASS.