Date: 2026-02-25 Version: 1.0.0 (INTERNAL REVIEW — no external audit) Classification: Internal review v1.0 (claims bounded by tests/specs) Last Security Review: 2026-02-25
This section is the authoritative threat model for the v1.0 internally-reviewed release.
Passive Observer
- Records the full GIF/QR stream.
- Performs offline cryptanalysis and traffic analysis.
Active Adversary
- Drops, reorders, replays, duplicates, or injects frames (Dolev‑Yao on channel).
- Supplies chosen input files to the encoder.
- Tamper with manifests and droplets.
Offline Brute‑Force
- Attempts password guesses against captured ciphertexts.
Local Memory Inspection (Limited)
- Can snapshot process memory while encode/decode runs.
- Does not control kernel/hardware (no DMA, no power/EM side‑channels).
🔮 Quantum Harvest Adversary (Harvest-Now-Decrypt-Later)
- Records all GIF/QR traffic for future quantum decryption.
- Stores encrypted payloads indefinitely (decades).
- Assumes fault-tolerant quantum computer in 10-30 years.
- Mitigation: ML-KEM-768 + X25519 PQXDH hybrid (requires
--pqflag), ML-KEM-1024 + X25519 available as --paranoid mode. - Status: ✅ PROTECTED only when
--pqflag is explicitly used. Default config (MEOW3) uses X25519 only (classical, vulnerable to quantum harvest).
🔬 Side-Channel Adversary (Cache/Timing)
- Measures CPU cache timing during crypto operations.
- Observes memory access patterns via
/procor shared caches. - Does NOT have physical access to device (no power/EM attacks).
- Mitigation: Rust
subtlecrate for constant-time ops, random jitter. - Status:
⚠️ MITIGATED (best-effort, not formally proven).
📡 Remote Timing Adversary (Network-Based)
- Measures response times over network to deduce passwords.
- Performs statistical analysis over many requests.
- Mitigation: Argon2id (memory-bound = noisy), timing equalization.
- Status:
⚠️ MITIGATED (Python limitations, ~1-5ms jitter applied).
- Plaintext confidentiality.
- Integrity of manifest and ciphertext.
- Keys and salts.
- Metadata obfuscation (size class, not exact size).
- Duress/decoy behavior (optional).
- Encoder: trusted to generate keys, nonces, and manifest format.
- Decoder: trusted to enforce auth‑then‑output.
- Optical channel: fully untrusted.
- Capture device (phone): partially trusted — Meow Capture app (iOS/Android) enforces the zero-network invariant (
INTERNETpermission never granted) and wipes frame buffers on background; raw video recording on an uncontrolled device is outside this model. - User environment: assumed uncompromised OS and storage.
- Compromised hosts (malware/rootkits).
- Hardware side‑channels (power/EM/cache timing).
- Steganography indistinguishability under forensic analysis.
- Legal/physical coercion beyond duress/decoy behavior.
- Confidentiality: No plaintext without correct credentials.
- Integrity: Manifest/ciphertext tampering is detected before output.
- Authentication: Invalid frames are rejected cheaply (frame MACs).
- Fail‑Closed: No partial plaintext on error.
- Plausible Deniability (optional): Duress password yields decoy data.
- Verified (tests/formal models):
- Auth‑then‑output (no plaintext without HMAC+AEAD).
- Frame MAC rejection for tampered frames.
- Duress tag verification before expensive KDF.
- Assumed:
- AES‑GCM security.
- Argon2id resistance.
- OS RNG quality.
| Metadata Type | Leakage Vector | Mitigation | Status |
|---|---|---|---|
| File Size | Frame count (k_blocks × redundancy) | Bucketed padding (--paranoid) |
|
| File Type | None (encrypted) | N/A | ✅ Fully hidden |
| Timestamp | GIF creation date | Remove EXIF with exiftool |
|
| Encryption Mode | Manifest version byte | Constant across all files | |
| Forward Secrecy | Ephemeral pubkey presence (32 bytes) | Always present in MEOW3+ | |
| Steganography | Frame pattern analysis | Layer-2 cat carrier images | ✅ Hidden unless analyzed deeply |
| Password Strength | None (Argon2id resistant) | N/A | ✅ No timing oracle |
# Attacker can estimate size from frame count
qr_frames = count_frames_in_gif(gif)
k_blocks = (qr_frames - 1) / redundancy # Minus manifest frame
approx_size = k_blocks * block_size
# Example: 180 frames, redundancy=1.5, block_size=512
# k_blocks = (180 - 1) / 1.5 = 119
# approx_size = 119 * 512 = ~61 KBAccuracy: ±50% due to compression, padding, block size variations
Default Mode (Automatic Padding):
- Compressed data padded to next power-of-2
- Example: 1.3 MB → 2 MB, 5.1 MB → 8 MB
- Leakage: Size class (1-2 MB, 2-4 MB, 4-8 MB, etc.)
- Protection: Prevents exact size fingerprinting
Paranoid Mode (--paranoid):
meow-encode --paranoid -i secret.pdf -o secret.gif -p "password"- Fixed buckets: 1 MB, 4 MB, 16 MB, 64 MB, 256 MB
- Chaff frames added to match bucket
- Leakage: Bucket only (e.g., "4-16 MB range")
- Protection: Maximum size obfuscation
Steganography Mode (Layer 2):
meow-encode --stego-level 4 -i secret.pdf -o cat_photos.gif -p "password"- QR codes hidden in photographic cat images
- Frame count appears natural (vacation photos)
- Leakage: Appears as normal GIF (20-50 MB typical)
- Protection: Hides presence of encrypted data
| Attack | Mitigation | Effectiveness |
|---|---|---|
| GIF size on wire | Compress/archive after encoding | |
| Frame timing | Constant rate (10 FPS default) | ✅ Good (no timing patterns) |
| Carrier detection | Steganography mode | |
| Frequency analysis | Entropy-tested mixers |
What's Protected:
- ✅ File contents (AES-256-GCM)
- ✅ File type (compressed then encrypted)
- ✅ Password (Argon2id, no oracle)
- ✅ Exact size (bucketed padding)
What's Visible:
⚠️ Approximate size class (via frame count)⚠️ Encryption used (manifest magic bytes)⚠️ Meow Decoder used (QR patterns unless stego)
Recommendation for Maximum Privacy:
# Combine all mitigations
meow-encode --paranoid --stego-level 4 \
--chaff-frames 30 \
-i secret.pdf -o innocent_cats.gif -p "strong_password"
# Then remove EXIF metadata
exiftool -all= innocent_cats.gifShort Answer: No. Here's why:
| Requirement for NSA Resistance | Meow Decoder Status |
|---|---|
| Formal verification (mathematical proof of correctness) | ✅ Partial — guard-page memory safety (Verus GB-001–GB-008); AEAD abstract properties (Verus AEAD-001–AEAD-004 in verus_proofs.rs); structural binding lemmas in aead_wrapper.rs |
| Independent security audit by cryptographers | ⭕ Seeking funding |
| Certified constant-time implementation (no timing leaks) | ✅ Rust backend (subtle crate) |
| Side-channel resistance (power, EM, cache) | |
| Hardware security module integration | ✅ TPM/YubiKey support |
| Secure element / TEE support | ⭕ Planned |
| Post-quantum crypto (production-ready) | ✅ ML-KEM-768 (--pq flag) / ML-KEM-1024 (--paranoid) + Dilithium3 — not default; requires explicit opt-in |
| Zero-knowledge proofs for deniability |
However: The cryptographic primitives we use (AES-256-GCM, Argon2id, X25519, ML-KEM-768/1024, Dilithium3) are state-of-the-art. Rust backend provides constant-time operations.
What Would Be Needed:
- Rewrite in Rust/C with formal verification
- Use hardware security modules (HSMs)
- Professional security audit ($50K-$200K+)
- Side-channel resistant hardware
- True constant-time implementation via crypto libraries written in C
This section enumerates concrete attack surfaces and the current mitigations implemented in the codebase.
| Surface | Risk | Mitigation | Status |
|---|---|---|---|
| GIF/QR decoding | Malformed frames or decode crashes | Frame MACs + redundancy; drop invalid frames (meow_decoder/frame_mac.py) | ✅ Implemented |
| Manifest parsing | Truncated/corrupted manifest | Strict length checks + HMAC verification (meow_decoder/decode_gif.py, meow_decoder/crypto.py) | ✅ Implemented |
| Keyfile loading | Malformed or huge keyfile | Size checks (32B–1MB) (meow_decoder/crypto.py) | ✅ Implemented |
| Surface | Risk | Mitigation | Status |
|---|---|---|---|
| Nonce reuse | GCM catastrophic failure | Fresh random nonce + per‑process reuse guard + Rust CAS loop counter (prevents u64 wrap) (meow_decoder/crypto.py) | ✅ Implemented |
| Metadata tampering | Length/hash substitution | AES‑GCM AAD binds fields; manifest HMAC (meow_decoder/crypto.py, meow_decoder/crypto.py) | ✅ Implemented |
| Frame injection | DoS or decode confusion | Per‑frame MAC (8 bytes) (meow_decoder/frame_mac.py) | ✅ Implemented |
| Key reuse across domains | Cross‑protocol attacks | HKDF domain separation + HMAC prefixes (meow_decoder/crypto.py) | ✅ Implemented |
| Surface | Risk | Mitigation | Status |
|---|---|---|---|
| Cross‑session replay | Old frames accepted | Frame MAC derives from per‑session key material (meow_decoder/frame_mac.py) | ✅ Implemented |
| Password‑only + duress ambiguity | Manifest size collision | Duress requires FS/PQ (meow_decoder/encode.py) | ✅ Implemented |
| Surface | Risk | Mitigation | Status |
|---|---|---|---|
| Duress path leaks real data | Coercion failure | Decoy generated without decrypting real ciphertext (meow_decoder/decode_gif.py) | ✅ Implemented |
| Duress timing oracle | Password probing | Constant‑time comparison + jitter (meow_decoder/crypto.py, meow_decoder/constant_time.py) | ✅ Implemented |
| Surface | Risk | Mitigation | Status |
|---|---|---|---|
| Compromised endpoint | Keys/plaintext exposed | Out of scope (OS hardening) | ❌ Out of scope |
| Screen recording | Visible QR frames | Steganography (cosmetic), operational security |
Notes:
- This analysis is aligned with docs/protocol.md.
- Formal methods are summarized in the formal/ directory (Tamarin, ProVerif, Lean 4, TLA+ models).
This section maps security claims to formal verification artifacts.
| Property | Formal Method | Artifact | Coverage |
|---|---|---|---|
| Auth-then-Output | TLA+ (TLC) | formal/tla/meow_protocol.tla |
✅ Verified (bounded) |
| Replay Rejection | TLA+ (TLC) + ProVerif | formal/tla/ + formal/proverif/ |
✅ Verified (symbolic) |
| Guard-page memory safety | Verus | crypto_core/src/verus_guarded_buffer.rs (GB-001–GB-008) |
✅ Verified (real Verus proofs) |
| Nonce Uniqueness | Verus + Type system | verus_proofs.rs (lemma_nonce_uniqueness) + UniqueNonce (linear type) |
✅ Verus-proven (abstract) + Type-enforced |
| Auth-Gated Plaintext | Verus + Type system | verus_proofs.rs (lemma_auth_gated_plaintext) + AuthenticatedPlaintext (private constructor) |
✅ Verus-proven (abstract) + Type-enforced |
| Key Zeroization | Verus + zeroize crate |
verus_proofs.rs (lemma_key_zeroization) + ZeroizeOnDrop (volatile writes) |
✅ Verus-proven (abstract) + Runtime-enforced |
| No-Bypass (nonce consumption) | Verus + Rust ownership | verus_proofs.rs (lemma_no_bypass) + UniqueNonce consumed by encrypt() |
✅ Verus-proven (abstract) + Ownership-enforced |
| Frame MAC Integrity | TLA+ | formal/tla/meow_protocol.tla |
✅ Verified |
| Duress Behavior | TLA+ | formal/tla/meow_protocol.tla |
✅ Verified |
| HW Key Isolation | TLA+ | formal/tla/meow_protocol.tla (HWKeyNeverExposed) |
✅ Verified |
Verification note: The AEAD properties (AEAD-001 through AEAD-004) are verified at two levels: (1) abstract Verus proofs in
verus_proofs.rsuse spec functions (nonce_monotonic,auth_gated,bytes_zeroed) checked by Z3; (2) structural binding lemmas inaead_wrapper.rs(noassume(false)) connect the abstractions to the concrete module types. Guard-page memory safety proofs (GB-001–GB-008 inverus_guarded_buffer.rs) provide a deeper end-to-end machine-checked verification. AEAD correctness additionally relies on theaes-gcmcrate’s proven security, Rust’s affine type system, and comprehensive property-based testing.
| Property | Method | Status |
|---|---|---|
| Dolev-Yao Secrecy | ProVerif | ✅ Verified (event(DecryptOK) reachable only with key) |
| Dolev-Yao Authentication | ProVerif | ✅ Verified (manifest bound to password) |
| Plausible Deniability | Tamarin (observational equiv.) |
| Attack Class | Mitigation | Test Coverage | Status |
|---|---|---|---|
| Timing (password compare) | secrets.compare_digest |
tests/test_sidechannel.py |
✅ Tested |
| Timing (HMAC verify) | secrets.compare_digest |
tests/test_sidechannel.py |
✅ Tested |
| Timing (duress check) | Constant-time + jitter | tests/test_sidechannel.py |
✅ Tested |
| Cache timing (AES) | Rust aes-gcm (bitsliced) |
Assumed (crate audit) | |
| Memory leakage | zeroize crate |
tests/test_sidechannel.py |
✅ Tested |
For a complete pre-audit checklist, see: SECURITY_INVARIANTS.md
For operational security best practices, see: USAGE.md
| Side-Channel | Attack | Mitigation | Location | Effectiveness |
|---|---|---|---|---|
| Timing | Password timing oracle | secrets.compare_digest |
crypto.py:L373 |
✅ Strong |
| Timing | HMAC verification timing | secrets.compare_digest |
crypto.py:L1273 |
✅ Strong |
| Timing | Duress detection timing | Timing equalization (1-5ms) | constant_time.py:L125 |
|
| Timing | Frame MAC verification | Constant-time compare | frame_mac.py:L89 |
✅ Strong |
| Memory | Key residue in RAM | SecureBuffer + zeroize |
constant_time.py:L226 |
|
| Memory | Password residue | secure_zero_memory() |
constant_time.py:L55 |
|
| Cache | AES T-table attacks | Bitsliced AES (Rust crate) | crypto_core/src/lib.rs |
✅ Strong |
| Power/EM | Key extraction | NOT IMPLEMENTED | — | ❌ Out of scope |
Side-channel resistance is tested in CI via:
# Run side-channel test suite
make sidechannel-test
# Individual tests
pytest tests/test_sidechannel.py -v
# Tests include:
# - TestConstantTimeComparison
# - TestFrameMACTiming
# - TestKeyDerivationTiming
# - TestDuressTimingEqualization
# - TestSecureMemoryZeroing| Limitation | Reason | Mitigation Path |
|---|---|---|
| Python GC | Garbage collector may leave key copies | Use Rust backend exclusively |
| OS scheduling | Thread preemption affects timing | Statistical noise |
| PyPy JIT | Compilation affects timing | Not supported |
| Core dumps | Memory captured if crash | Disable core dumps |
| Swap | Keys may be written to disk | mlock() + encrypted swap |
Meow Decoder offers optional steganographic carrier modes that embed QR frames within photographic or branded imagery. This section defines the adversary model, attack surface, and security boundaries for these modes.
Non-goal reminder: Full steganographic indistinguishability under forensic analysis is explicitly out-of-scope (see Non-Goals). Stego modes provide cosmetic cover against casual observation, not against a determined forensic analyst with access to the carrier image.
| Adversary | Capabilities | In Scope? |
|---|---|---|
| Casual observer | Sees GIF on screen/phone; no domain expertise | ✅ Yes — stego defeats this |
| Automated scanner | Content-type sniffing, file-header inspection | ✅ Yes — GIF header is valid |
| Statistical analyst | Chi-squared, RS analysis, sample pairs | |
| ML classifier | Trained CNN/GAN steganalysis (e.g., SRNet, YeNet) | ❌ No — not designed to resist |
| Known-carrier attacker | Has original un-embedded carrier image | ❌ No — trivially detectable |
| Forensic lab | Full toolchain: EnCase, Autopsy, StegExpose, binwalk | ❌ No — will detect embedding |
| Mode | Implementation | Visual Cover | Detection Resistance |
|---|---|---|---|
| Plain QR | Standard black/white QR frames | ❌ None — obviously encoded data | ❌ None |
| Cat camouflage | QR embedded in cat-photo carrier frames | ✅ Casual — looks like cat GIF | |
| Logo-eyes | QR data placed in eye regions of branded logo | ✅ Casual — looks like animated logo | |
| Cat-eyes blink | Green pixel blinking pattern over cat image | ✅ Good — subtle visual change |
Attack: Measure deviations from expected pixel-value distributions in least-significant bits.
Vulnerability: Cat camouflage mode embeds QR data into LSBs of carrier images. Tools like StegExpose or zsteg can detect non-random LSB distributions with high confidence for embedding rates > 0.5 bits/pixel.
Mitigation: None implemented. The QR payload is large relative to carrier image capacity, resulting in high embedding rates that are statistically detectable.
Status:
Attack: Train a convolutional neural network (e.g., SRNet, Zhu-Net) on Meow-encoded vs clean GIFs.
Vulnerability: The fixed QR structure creates a learnable spatial signature. Even with carrier randomization, the QR error-correction patterns are distinctive.
Mitigation: None. ML steganalysis is beyond the design scope. An adversary who knows the tool exists can train a classifier with near-100% accuracy.
Status: ❌ Out of scope.
Attack: Analyze frame-to-frame pixel differences in the GIF. Normal cat GIFs have smooth temporal coherence; Meow-encoded GIFs show abrupt frame-by-frame changes in QR regions.
Vulnerability: QR content changes every frame (fountain code droplets), creating temporal discontinuities that are trivially distinguishable from natural animation.
Mitigation: Cat-eyes blink mode reduces the embedded region to a small area, making temporal anomalies less conspicuous. However, periodic blinking patterns are still detectable via frequency analysis.
Status:
Attack: Inspect GIF structure, frame timing, color palette, and metadata for anomalies.
Vulnerability: Meow-encoded GIFs use uniform 100ms frame timing (10 FPS) and may have unusual color palettes (QR black/white mixed with photo colors). Frame count may be unusually high for a "cat photo."
Mitigation: Valid GIF headers and structure. No Meow-specific metadata in file. Frame timing is configurable.
Status:
Attack: Adversary obtains (or generates) the original un-embedded carrier image and computes pixel differences.
Vulnerability: Trivially reveals all embedded data locations. XOR of carrier and stego image shows exact QR frame positions.
Mitigation: None possible — this is a fundamental limitation of all steganographic systems.
Status: ❌ Fundamental — cannot be mitigated.
| Property | Guaranteed? | Notes |
|---|---|---|
| Cryptographic confidentiality | ✅ Yes | AES-256-GCM protects payload regardless of stego mode |
| Casual visual cover | ✅ Yes | Non-expert observers see cat photos / logos |
| Automated content-filter bypass | ✅ Likely | Valid GIF, no flagged keywords/headers |
| Resist forensic steganalysis | ❌ No | Statistical and ML detectors will identify embedding |
| Resist targeted investigation | ❌ No | Known tool → known carrier patterns |
| Plausible deniability of stego | ❌ No | Stego does NOT provide deniability — use Schrödinger mode |
- Do not rely on steganography for security. It provides cosmetic cover only.
- For limited plausible deniability, use Schrödinger mode (dual-secret encoding), which provides deniability under casual inspection but NOT against nation-state forensic analysis or comparison of multiple samples.
- For maximum stealth, combine stego carrier with Schrödinger mode and operational security (no tool binaries on device, ephemeral boot media). Even this combination is not proof against determined forensic analysis.
- Assume stego is broken if the adversary knows Meow Decoder exists and can obtain sample outputs.
The multi-layer steganography system underwent a comprehensive adversarial security review. This section documents the updated threat posture after 8 bug fixes and 268 stego tests.
| Channel | Technique | Detection Resistance | Adversarial Review Status |
|---|---|---|---|
| Primary (LSB+STC) | Keyed pseudorandom LSB walk with Syndrome-Trellis Codes | ✅ STC algorithm rewritten, roundtrip verified | |
| Timing | GIF inter-frame delay modulation | ✅ Tested | |
| Palette | GIF palette entry permutation | ✅ Encode/decode rewritten, roundtrip verified |
| Bug | Severity | Threat Before Fix |
|---|---|---|
| AES-GCM zero-nonce reuse | 🔴 CRITICAL | Complete cipher break — XOR of all stego plaintexts revealed |
| Encryption fail-open | 🔴 CRITICAL | Plaintext embedded without encryption if Rust backend absent |
| Python↔Rust seed mismatch | 🔴 CRITICAL | Cross-backend decode failure — data loss |
| STC algorithm broken | 🔴 CRITICAL | Encoded payloads unrecoverable — data loss |
| Palette encode NO-OP | 🟠 HIGH | Palette channel carried zero information |
| Palette decode identity | 🟠 HIGH | Palette channel undecodable |
| Payload > capacity warn-only | 🟠 HIGH | Silent data truncation |
| Fisher-Yates modulo bias | 🟡 MEDIUM | Biased pseudorandom walk — detectable statistical anomaly |
| Property | Before Review | After Review |
|---|---|---|
| Cryptographic confidentiality | ❌ Broken (nonce reuse) | ✅ Guaranteed (fresh nonces) |
| Fail-closed encryption | ❌ Fail-open | ✅ Fail-closed (RuntimeError) |
| STC payload integrity | ❌ Broken (algorithm bug) | ✅ Verified (Viterbi trellis, rate 1/4, 100% reliable) |
| Cross-backend consistency | ❌ Broken (seed mismatch) | ✅ Verified (HKDF parity) |
| Capacity enforcement | ❌ Warn-only | ✅ Fail-closed (ValueError) |
| Resist casual observation | ✅ Yes | ✅ Yes |
| Resist statistical steganalysis | ✅ Verified (RS<0.05, Chi²=0, SPA<0.02 across 43 artifacts) | |
| Resist ML steganalysis | ❌ Not designed for | ❌ Not designed for |
The following claims are backed by empirical testing across 43 artifacts with 5 different
carrier images, multiple payload sizes (64B–10KB), and all encoding modes. See
docs/STEGO_AUDIT_REPORT.md for full methodology and raw data.
| Detector | Metric | Max Observed | Threshold | Status |
|---|---|---|---|---|
| RS Analysis | detection probability | 0.048 | < 0.3 | ✅ PASS |
| Chi-Square | detection value | 0.000 | < 0.3 | ✅ PASS |
| SPA | estimated embedding rate | 0.015 | < 0.15 | ✅ PASS |
| LSB autocorrelation | pattern coefficient | 0.024 | < 0.05 | ✅ PASS |
| Visual quality (PSNR) | peak signal-to-noise | 36.2 dB (min) | > 30 dB | ✅ PASS |
| Visual quality (SSIM) | structural similarity | 0.9978 (min) | > 0.99 | ✅ PASS |
Important caveats:
- ML Classifiers (SRNet, YeNet): NOT designed to resist. Custom-trained classifiers on Meow Decoder output will likely detect embedding. This is a non-goal.
- Known-carrier attacks: If the adversary has the original un-embedded carrier image, pixel differencing trivially reveals embedding. Fundamental limitation of all stego systems.
- Timing Analysis: GIF delay modulation is a weak channel; statistical tests on frame timing distributions may reveal anomalies.
- High capacity (>80%): At near-maximum embedding rates, statistical metrics increase. Recommended operating point is <50% capacity.
For detailed comparison against other tools, see docs/STEGO_STRENGTH_EVALUATION.md.
| User Profile | Protection Level | Notes |
|---|---|---|
| 👤 Personal privacy | ✅ EXCELLENT | Strong encryption, easy to use |
| 📰 Journalist (sources) | ✅ STRONG | Forward secrecy, limited plausible deniability (casual inspection only) |
| 🏢 Business confidential | ✅ GOOD | Professional-grade crypto |
| 🌍 Activist (non-state threat) | Use with operational security | |
| 🏛️ Government classified | ❌ INSUFFICIENT | Use certified tools |
| 🎯 Nation-state target | ❌ INSUFFICIENT | Use Signal + hardware isolation |
These protections are based on well-understood cryptographic primitives with no known practical attacks:
| Aspect | Implementation | Strength |
|---|---|---|
| Encryption | AES-256-GCM | 256-bit security, NIST approved |
| Key Exchange | X25519 | 128-bit security, widely audited |
| Authentication | GCM auth tag + HMAC-SHA256 | Cryptographically secure |
| Status | ✅ STRONG | No practical attack exists |
| Aspect | Implementation | Strength |
|---|---|---|
| KDF | Argon2id | Memory-hard, GPU/ASIC resistant |
| Memory | 512 MiB | 8x OWASP minimum |
| Iterations | 20 passes | ~5-10 seconds per attempt |
| Status | ✅ ULTRA | 10^18+ attempts infeasible |
Brute-Force Mathematics (v1.0):
| Scenario | Cost per Attempt | Attempts/Sec | Years to Crack 20-char Password |
|---|---|---|---|
| Single GPU (RTX 4090) | $2 | ~0.1 | 10^35 years |
| GPU Farm (1000 GPUs) | $5M | ~100 | 10^32 years |
| Nation-state (exascale) | $1B | ~10^6 | 10^28 years |
| Quantum (Grover) | ??? | N/A | Still 10^14 years (AES-256 → 128-bit) |
Why 512 MiB / 20 iterations?
- GPU memory bandwidth bottleneck (even RTX 4090 struggles)
- ASIC development cost exceeds value of most secrets
- Cloud cracking cost: ~$50M per password for 12-char random
| Aspect | Implementation | Strength |
|---|---|---|
| Ciphertext integrity | AES-GCM auth tag | 128-bit authentication |
| Key commitment | HMAC-SHA256 commitment tag (MSR v1.2) | Prevents invisible salamanders |
| Manifest integrity | HMAC-SHA256 + AAD | Cryptographically bound |
| AAD construction | Canonical AAD (canonical_aad.py) |
Deterministic, version-aware |
| Frame integrity | Per-frame 8-byte MAC | Prevents injection |
| Frame header privacy | Header encryption (MSR v1.2) | Frame indices XOR-masked |
| Chunk integrity | Merkle tree | Efficient verification |
| Tamper forensics | --tamper-report CLI flag |
Frame-by-frame MAC timeline with cluster detection |
| Status | ✅ STRONG | Any modification detected |
| Aspect | Implementation | Strength |
|---|---|---|
| Error correction | Luby Transform fountain codes | Rateless, optimal |
| Redundancy | 1.5x default (configurable) | Tolerates 33% loss |
| Integrity | Merkle tree verification | Per-chunk validation |
| Status | ✅ EXCELLENT | Decode from any sufficient subset |
Schrödinger mode attempts to encode two independent secrets into one output file. Revealing one password shows a plausible complete payload (decoy or real). Cryptographic deniability is limited: while the two encodings are designed to look superficially similar, advanced statistical analysis, timing differences, or comparison of multiple files from the same user may allow a nation-state forensic team to detect the presence of dual encoding or link transfers. This is plausible deniability under casual inspection, not perfect cryptographic deniability against unlimited compute and multiple samples. Duress/panic password triggers decoy reveal + key wipe, but memory/swap/forensic recovery may still expose keys if not done perfectly. Do not rely on this feature alone against a determined state adversary.
| Aspect | Implementation | Honest Strength |
|---|---|---|
| Dual secrets | Independent AES-256-GCM per stream | Two valid decryptions ✅ |
| Statistical hiding | Both streams always present (dummy if single-secret) | Casual inspection only |
| Forensic resistance | Entropy/chi-square tested | NOT forensic-proof ❌ |
| Timing isolation | Best-effort equalized paths | Timing side-channels possible ❌ |
| Multi-file linkability | Same tool signatures | Linkable across samples ❌ |
| Status | Casual deniability, NOT nation-state proof |
What works:
- Each password independently decrypts only its sub-stream
- Both sub-streams always present (even in single-secret mode, a random dummy fills the other)
- Independent Argon2id, GCM keys, and fountain seeds per stream
- No cross-commitments between streams
What does NOT work against a determined state adversary:
- Comparison of multiple files from the same user reveals patterns
- File size, frame count, and timing may leak dual-encoding presence
- Memory/swap forensics may recover both key sets
- Forensic tools specifically targeting Meow Decoder output format
- Physical torture / legal compulsion to reveal both passwords
| Aspect | Implementation | Strength |
|---|---|---|
| Key agreement | X25519 ephemeral keys | Per-encryption fresh keys |
| Zero-check | All-zero shared secret rejected (small-subgroup safety) | Rust constant-time check |
| Key destruction | Keys never stored | Destroyed after use |
| Compromise resistance | Past messages protected | Future leak can't decrypt past |
| Status | ✅ STRONG | True forward secrecy |
| Aspect | Implementation | Strength |
|---|---|---|
| Frame MAC | HMAC-SHA256 truncated to 8 bytes | Per-frame authentication |
| Verification | Constant-time comparison | No timing leaks |
| Rejection | Invalid frames ignored | DoS prevention |
| Status | ✅ STRONG | Malicious frames rejected |
| Aspect | Implementation | Strength |
|---|---|---|
| Length padding | Power-of-2 size classes | Hides true file size |
| Frame obfuscation | Randomized padding | Uniform appearance |
| Status | ✅ IMPLEMENTED | Size fingerprinting prevented |
Problem: File sizes can fingerprint content types (e.g., a 3.2 MB file is likely a photo, 847 KB is likely a document).
Solution: Length padding rounds compressed data to size classes, hiding true file size.
Default Mode (Automatic):
- Compressed data is padded to the next power-of-2 boundary
- Example: 1.3 MB → 2 MB (padded), 5.1 MB → 8 MB (padded)
- Provides ~50% size obfuscation on average
Paranoid Mode (--paranoid):
For maximum metadata protection, use paranoid mode which pads to fixed size buckets:
# Enable paranoid metadata padding
meow-encode --paranoid -i secret.pdf -o secret.gif -p "password"| Original Size | Default Padding | Paranoid Padding |
|---|---|---|
| 100 KB | 128 KB | 1 MB |
| 500 KB | 512 KB | 1 MB |
| 1.5 MB | 2 MB | 4 MB |
| 7 MB | 8 MB | 16 MB |
| 20 MB | 32 MB | 64 MB |
Paranoid Size Buckets: 1 MB, 4 MB, 16 MB, 64 MB, 256 MB
Trade-off: Paranoid mode increases GIF size significantly but makes size-based traffic analysis much harder.
When to Use Paranoid Mode:
- Transferring documents that could be identified by size
- Adversary has statistical knowledge of your file patterns
- Maximum metadata protection is required
Implementation: See meow_decoder/metadata_obfuscation.py
These threats have mitigations but cannot be fully eliminated due to fundamental limitations:
Current Status: Production-ready hybrid encryption
| Aspect | Implementation | Status |
|---|---|---|
| Symmetric encryption | AES-256 (Grover: 128-bit effective) | ✅ Quantum-resistant |
| Key derivation | Argon2id | ✅ Quantum-resistant |
| Key exchange | ML-KEM-768/1024 (Kyber) + X25519 PQXDH | ✅ Production |
What's Implemented:
pq_hybrid.pywith ML-KEM-768 + X25519 (default) / ML-KEM-1024 (paranoid) — primary PQ module (experimental, not externally audited)pq_crypto_real.pyis deprecated (emitsDeprecationWarning, forced to ML-KEM-1024)- All PQ crypto routes through the Rust backend (
meow_crypto_rs) in production - Security: Safe if EITHER classical OR quantum crypto holds
- Dilithium3 signatures for quantum-resistant manifest authentication (FIPS 204)
Manifest Signature Policy (Important):
- Manifest signing is strongly recommended and enabled by default when backend support is available.
- Decoder compatibility mode accepts unsigned manifests with a warning.
- Unsigned manifests are vulnerable to manifest forgery attacks. Always enable signing for production/high-risk use.
- If a signature is present but invalid, decode fails closed.
How to Enable PQ Mode:
# PQ mode is ON by default (enable_pq=True in config.py).
# All PQ crypto routes through the Rust backend (meow_crypto_rs).
# No separate Python library installation is required.
meow-encode -i secret.pdf -o secret.gif -p "password"
# Paranoid mode (ML-KEM-1024 instead of the default ML-KEM-768):
meow-encode --paranoid -i secret.pdf -o secret.gif -p "password"Note: ML-KEM-768 is the default; ML-KEM-1024 available as --paranoid. PQ mode is experimental.
Current Status: Platform-dependent
| Aspect | Implementation | Platform Support |
|---|---|---|
| Memory locking | mlock() via ctypes | Linux ✅, macOS |
| Secure zeroing | SecureBytes + gc.collect | All platforms (best-effort) |
| Swap prevention | mlock when available | Linux only reliably |
What's Implemented:
constant_time.py: SecureBuffer with mlock and zeroing- Rust backend:
zeroizecrate for automatic secret key cleanup - Automatic cleanup on context exit
Limitations:
- Python garbage collector may leave copies
- Core dumps can capture memory
- Cold boot attacks on DRAM possible
- mlock requires elevated privileges on some systems
How to Upgrade to STRONG:
# Run with elevated privileges for mlock
sudo python -m meow_decoder.encode -i secret.pdf -o secret.gif
# Use encrypted swap
sudo cryptsetup create swap_crypt /dev/sdXX
# Disable core dumps
ulimit -c 0
echo 0 | sudo tee /proc/sys/kernel/core_patternCurrent Status: Best-effort in Python
| Aspect | Implementation | Status |
|---|---|---|
| Password comparison | secrets.compare_digest | ✅ Constant-time |
| HMAC verification | secrets.compare_digest | ✅ Constant-time |
| Timing equalization | Random delays (1-5ms) | |
| Key derivation | Argon2id (memory-bound) |
Fundamental Limitation: Python cannot guarantee true constant-time execution due to:
- Garbage collection pauses
- JIT compilation (PyPy)
- OS scheduling
- Memory allocation
What We Do:
- Use
secrets.compare_digesteverywhere - Add random timing jitter after operations
- Memory-bound operations naturally obscure timing
How to Upgrade to STRONG: Would require rewriting critical paths in C/Rust with verified constant-time code.
These threats cannot be mitigated by software alone:
- Why: Optical channel is inherently visible
- Mitigation: Operational security (private environment)
- Consider: Steganography mode (hides QR in images)
- Why: Cannot protect against compromised OS
- Mitigation: Use air-gapped, trusted hardware
- Consider: Tails OS, QubesOS, hardware tokens
- Why: Requires hardware-level mitigation
- Mitigation: Faraday cages, side-channel resistant CPUs
- Consider: Hardware security modules
- Why: Legal systems can compel disclosure
- Mitigation: Schrödinger mode provides limited casual deniability
- Note: Jurisdiction-dependent, NOT foolproof. A competent forensic team can detect dual encoding through statistical analysis or comparison of multiple files.
- Why: Physical coercion defeats all crypto
- Mitigation: Schrödinger decoy password provides a cover story
- Note: Provides cover story only, NOT full protection. Do not rely on this feature alone against a determined state adversary with forensic capabilities.
Already enabled out of the box:
- ✅ AES-256-GCM encryption
- ✅ Argon2id (512 MiB, 20 iterations)
- ✅ Forward secrecy (X25519)
- ✅ Frame MAC authentication
- ✅ Metadata padding
- ✅ Rust crypto backend (constant-time via
subtle, zeroing viazeroize) - ✅ Post-quantum crypto (ML-KEM-768 via
--pq/ ML-KEM-1024 via--paranoid— requires explicit flag, not default)
For even higher security (if you have the hardware):
# In config.py or via CLI
config.crypto.argon2_memory = 524288 # 512 MiB (already default)
config.crypto.argon2_iterations = 20 # 20 passes (already default)
config.encoding.redundancy = 2.5 # Higher error toleranceCLI:
meow-encode -i secret.pdf -o secret.gif \
--argon2-memory 524288 \
--argon2-iterations 20 \
--redundancy 2.5For long-term archival / journalist sources:
# PQ crypto is handled by the Rust backend (meow_crypto_rs).
# No separate installation required.
# Use Schrödinger mode + PQ + enhanced Argon2
meow-schrodinger-encode \
--real classified.pdf \
--decoy vacation.zip \
--pq \
--argon2-memory 524288 \
--argon2-iterations 15 \
-o quantum.gif# 1. Use air-gapped machine running Tails
# 2. Maximum Argon2 parameters
export MEOW_ARGON2_MEMORY=1048576 # 1 GiB
export MEOW_ARGON2_ITERATIONS=20
# 3. PQ hybrid mode (handled by Rust backend, no extra install needed)
# ML-KEM-768 is default; --paranoid for ML-KEM-1024
# 4. Schrödinger dual secrets
meow-schrodinger-encode --pq ...
# 5. Securely wipe source after encoding
meow-encode --wipe-source ...
# 6. Shred temporary files
shred -u /tmp/meow_*| Attack Vector | Current | After Hardening | Notes |
|---|---|---|---|
| Passive Eavesdropping | ✅ STRONG | ✅ STRONG | AES-256-GCM |
| Brute Force | ✅ STRONG | ✅ EXCELLENT | Increase Argon2 params |
| Tampering | ✅ STRONG | ✅ STRONG | GCM + MAC + Merkle |
| Data Loss | ✅ EXCELLENT | ✅ EXCELLENT | Fountain codes |
| Coercion | ✅ UNIQUE | ✅ UNIQUE | Schrödinger mode |
| Forward Secrecy | ✅ STRONG | ✅ STRONG | X25519 ephemeral |
| Frame Injection | ✅ STRONG | ✅ STRONG | Per-frame MAC |
| Post-Quantum | ✅ STRONG | ✅ STRONG | ML-KEM-768/1024 PQXDH hybrid |
| Metadata Leak | ✅ IMPLEMENTED | ✅ STRONG | Size padding |
| Memory Forensics | ✅ STRONG | ✅ STRONG | mlockall + MADV_DONTDUMP + RLIMIT_CORE=0 |
| Timing Attacks | ✅ STRONG | ✅ STRONG | Timing equalizer + constant-time decode |
| OS Artifact Leakage | ✅ STRONG | ✅ STRONG | Forensic cleanup module |
| File Size Analysis | ✅ STRONG | ✅ STRONG | Power-of-4 size normalization |
| Timed Expiry | ✅ IMPLEMENTED | ✅ STRONG | Self-destruct on expiry |
| Screen Recording | ❌ NONE | ❌ NONE | Out of scope |
| Endpoint Compromise | ❌ NONE | ❌ NONE | Out of scope |
| Nation-State (NSA) | Needs formal audit |
| Adversary | Difficulty to Break | Requirements | Verdict |
|---|---|---|---|
| Script Kiddie | Impossible | Would need to break AES-256 | ✅ SECURE |
| Skilled Hacker | Extremely Hard | No known attack | ✅ SECURE |
| Criminal Organization | Very Hard | Massive resources needed | ✅ SECURE |
| Corporate Espionage | Hard | Memory forensics possible | |
| Law Enforcement | Moderate | Legal compulsion, forensics | |
| Intelligence Agency | Possible | Endpoint compromise, 0-days | |
| NSA (Full Resources) | Possible | All attack vectors available | ❌ NOT DESIGNED FOR |
For Meow Decoder to provide its stated security, these must be true:
-
Cryptographic Primitives Secure
- AES-256-GCM: No practical break (true as of 2026)
- Argon2id: Memory-hard, no shortcuts (true as of 2026)
- X25519: ECDH secure (true as of 2026)
- SHA-256: Collision-resistant (true as of 2026)
-
Implementation Correct
- Python
cryptographylibrary: Well-audited ✅ - Our code: Not audited
⚠️
- Python
-
Environment Secure
- No malware on endpoints
- OS not compromised
- Hardware not backdoored
-
User Behavior Secure
- Strong password chosen
- Keyfile kept secret (if used)
- Operational security maintained
- Rust crypto backend for true constant-time (73 PyO3 bindings,
subtle+zeroizecrates) - Hardware security module (HSM/PKCS#11) support — CLI-wired via
--hsm-slot - YubiKey PIV/FIDO2 support — CLI-wired via
--yubikey - TPM 2.0 binding — CLI-wired via
--tpm-derive - WebAuthn browser prototype (
examples/webauthn-hardware.js) - Post-quantum ML-KEM-768 (via
--pq) / ML-KEM-1024 (via--paranoid) via Rust backend - Opaque handle migration (M1–M9): Python never holds raw secret key bytes
- cargo-fuzz + property test suite for Rust crypto
- Memory guard:
mlockall(MCL_CURRENT|MCL_FUTURE)+RLIMIT_CORE=0+PR_SET_DUMPABLE=0 -
MADV_DONTDUMPon allSecureBuffer/secure_memory()allocations - Secure temp: tmpfs enforcement (
/dev/shmpreferred) withSecureTempDircontext manager - Forensic cleanup: thumbnails, recent-files, clipboard, shell history, GVFS metadata, temp files
- Timed expiry: 8-byte UTC expiry field with self-destruct on check
- Timing equalizer: constant wall-clock decode, forced dual-path, ±5% CSPRNG jitter
- Size normalizer: power-of-4 size classes (4 KB–64 MB), fixed frame count quantum
- Duress timing equalization: always run Argon2id even for duress path
- Fixed QR version (v25): prevents payload size leakage via QR structure
- Inter-file decorrelation: CSPRNG-randomized block_size, redundancy, fps, border, box_size
- Post-encode source cleanup: multi-pass overwrite + fsync + parent sync + TRIM hints
- Secure password input: pre/post random delays, non-TTY warnings
- Air-gap verification: network interfaces, DNS, WiFi, Bluetooth, default route checks
- Dual-stream flags byte always 0x00 (prevents deniability-killing distinguisher)
- Dual Argon2id in all decode paths (eliminates timing oracle)
-
PYTHONDONTWRITEBYTECODE=1in high-security mode
- Independent third-party security audit
- Formal verification of core crypto paths (Verus/Coq CI-gated proofs)
- FIPS 140-3 module validation (if demand exists)
- Side-channel resistant hardware integration (Faraday, power analysis countermeasures)
- Rust SecureBox with mlock + mprotect guard pages + MADV_DONTDUMP for key allocations
- PQ ratchet beacon: PQXDH-style hybrid root rekey (X25519 + ML-KEM-1024 combined into root key at each rekey frame, per
_fold_pq_into_rootinratchet.py) — post-quantum post-compromise security - Randomized interleave pattern in Schrödinger mode (HKDF-derived permutation)
- Security researchers: Open issues for vulnerabilities
- Cryptographers: Review implementation
- Rust developers: Help expand crypto core
Meow Decoder v1.0 provides:
| Category | Assessment |
|---|---|
| Cryptographic Strength | ✅ EXCELLENT - Uses best-in-class primitives |
| Implementation Quality | ✅ STRONG - Hardened with 16+ security modules, 300+ tests |
| Practical Security | ✅ STRONG - Protects against realistic threats |
| OS Hardening | ✅ STRONG - Memory guard, forensic cleanup, tmpfs, timing equalization |
| Anti-Forensics | ✅ STRONG - Source cleanup, size normalization, expiry, TRIM hints |
| Against Nation-States |
Honest Assessment:
- For personal, journalistic, and business use: Production-ready
- For activists and journalists (police forensics): STRONG with all hardening modules active
- For nation-state adversary with endpoint access: Use certified tools on air-gapped hardware
Remaining gap for 10/10: Rust-side SecureBox<T> with mlock + guard pages (keys in Rust allocator are not yet mlock'd), independent third-party audit, and formal verification of all code paths.
The math is solid. The implementation is hardened. The limitations are environmental and practical, not cryptographic.
Document Version: 1.3.0 Last Updated: 2026-02-21 Security Contact: Open a GitHub issue with [SECURITY] tag