phasm-core
Pure-Rust steganography engine for hiding encrypted text messages in JPEG photos.
This is the core library behind Phasm — available on iOS, Android, and the web.
Two Embedding Modes
Ghost (Stealth)
Optimizes for undetectability. Uses the J-UNIWARD cost function to assign distortion costs per DCT coefficient, then embeds via Syndrome-Trellis Codes (STC) to minimize total distortion. The result resists state-of-the-art steganalysis detectors (SRNet, XedroudjNet) at typical embedding rates.
Use Ghost when the stego image will be stored or transmitted without recompression — the embedding does not survive JPEG re-encoding.
Ghost mode also supports file attachments (Brotli-compressed, multi-file).
Shadow Messages (Plausible Deniability)
Ghost mode supports shadow messages — multiple messages hidden in a single image, each with a different passphrase. If coerced into revealing a passphrase, you can give the shadow passphrase instead of the primary one. The adversary decodes a decoy message and has no way to detect whether additional messages exist.
Shadows use Y-channel direct LSB embedding with Reed-Solomon error correction. Key design properties:
- Cost-pool position selection — shadow positions are drawn from the cheapest UNIWARD-cost regions via two-tier filtering (top-N% cost pool + keyed ChaCha20 permutation), ensuring modifications land in textured areas for maximum stealth
- ∞-cost protection — when the primary message uses dynamic w ≥ 2, shadow positions receive
f32::INFINITYcost in the STC Viterbi trellis, routing the primary encoder around them with BER ≈ 0% - Headerless brute-force decode — no magic bytes or agreed-upon parameters. A "first-block peek" decodes just the first RS block for each (fraction, parity) combination to read
plaintext_lenand derive the exact frame data length — ~30 RS block decodes instead of scanning thousands of FDL values. A small-FDL fallback handles tiny messages (single partial RS block). AES-256-GCM-SIV authentication is the only validator - Stego-cost verification — after STC embedding, the encoder re-runs UNIWARD on the stego image to verify shadow BER. If verification fails, an escalation cascade automatically increases RS parity (4 → 8 → 16 → … → 128) until the shadow survives or capacity is exhausted
smart_decode automatically tries shadow decode as a fallback after primary Ghost decode.
SI-UNIWARD (Deep Cover)
When the encoder has access to the original uncompressed pixels (e.g. PNG, HEIC, or RAW input converted to JPEG), SI-UNIWARD (Side-Informed UNIWARD) exploits JPEG quantization rounding errors to dramatically reduce embedding costs. Coefficients that were "close to the boundary" between two quantization levels can be flipped at near-zero cost, and the modification direction is chosen to move toward the pre-quantization value rather than the default nsF5 direction.
The result: ~43% higher capacity at the same detection risk, or equivalently the same capacity with significantly lower distortion. The decoder is completely unchanged — ghost_decode works identically on standard and SI-UNIWARD stego images.
Use ghost_encode_si / ghost_capacity_si when raw pixels are available alongside the JPEG cover.
Armor (Robust)
Optimizes for survivability. Uses Spread Transform Dither Modulation (STDM) to embed bits into stable low-frequency DCT coefficients, protected by adaptive Reed-Solomon ECC with soft-decision decoding via log-likelihood ratios. A DFT magnitude template provides geometric synchronization against rotation, scaling, and cropping.
Armor is the default mode on all platforms.
Fortress Sub-Mode
For short messages, Armor automatically activates Fortress — a BA-QIM (Block-Average Quantization Index Modulation) scheme that embeds one bit per 8x8 block into the block-average brightness. Fortress exploits three invariants of JPEG recompression — block averages, brightness ordering, and coefficient signs — that persist even through aggressive re-encoding.
Fortress survives WhatsApp recompression (QF ~62, resolution resize to 1600px). See how 15 messaging platforms process your photos for a comprehensive platform analysis.
Watson perceptual masking adapts the embedding strength per-block based on local texture energy, keeping modifications invisible in textured regions while protecting smooth areas.
Cover Image Optimizer
optimize_cover preprocesses raw pixels before JPEG compression to improve embedding quality and capacity. Each mode has a different pipeline:
- Ghost: Texture-adaptive 4-stage pipeline (noise injection, micro-contrast, unsharp mask, smooth-region dithering). Per-pixel 5×5 variance map adapts strength to existing texture — avoids degrading pre-optimized images.
- Armor: Light pipeline (block-boundary smoothing, DC stabilization) for STDM robustness.
- Fortress: Minimal (block-boundary smoothing only) to stabilize DC averages for BA-QIM.
"Do no harm" guarantee: if optimization reduces average gradient energy (a proxy for stego capacity), the original pixels are returned unchanged. Modifications are imperceptible (PSNR > 44 dB, SSIM > 0.993).
use ;
let optimized = optimize_cover;
Decode Auto-Detection
smart_decode tries all modes automatically — Ghost primary → Ghost shadow → Armor → Fortress — no mode selector needed on the decode side.
Which Mode Should I Use?
| Ghost | Armor | Fortress | |
|---|---|---|---|
| Goal | Undetectable | Survives recompression | Survives WhatsApp |
| Algorithm | J-UNIWARD + STC | STDM + Reed-Solomon | BA-QIM + Watson masking |
| Best for | Direct sharing, AirDrop, email, "send as file" | Social media, cloud storage, cross-platform | WhatsApp, messaging apps with aggressive compression |
| Capacity | High (~1 KB per 12 MP) | Medium | Low (short messages only) |
| Resists detection | Yes (steganalysis-resistant) | Moderate | Moderate |
| Resists recompression | No | Yes (>95% at mild QF drop) | Yes (survives QF ~62) |
| Plausible deniability | Yes (shadow messages) | No | No |
| File attachments | Yes (up to 2 MB) | No | No |
| Activation | --mode ghost |
Default | Auto (short messages in Armor) |
Rule of thumb: If the image stays intact → Ghost. If it gets recompressed → Armor. If it goes through WhatsApp → Fortress auto-activates.
Quick Start
use ;
let cover = read.unwrap;
let stego = ghost_encode.unwrap;
let decoded = ghost_decode.unwrap;
assert_eq!;
use ;
let cover = read.unwrap;
let stego = armor_encode.unwrap;
let = armor_decode.unwrap;
assert_eq!;
println!;
use ;
// SI-UNIWARD: when you have original uncompressed pixels + the JPEG cover
let raw_pixels_rgb = /* RGB pixel data from PNG/HEIC/RAW */;
let cover = read.unwrap;
let stego = ghost_encode_si.unwrap;
// Decode is identical — no special decoder needed
let decoded = ghost_decode.unwrap;
assert_eq!;
use ;
let cover = read.unwrap;
let shadows = vec!;
let stego = ghost_encode_with_shadows.unwrap;
// Primary message — with the real passphrase
let primary = ghost_decode.unwrap;
assert_eq!;
// Shadow message — with the decoy passphrase
let shadow = ghost_shadow_decode.unwrap;
assert_eq!;
API
// Ghost mode (stealth)
ghost_encode // Ghost shadow messages (plausible deniability)
ghost_encode_with_shadows // Ghost SI-UNIWARD (stealth + side-informed, ~43% more capacity)
ghost_encode_si // Armor mode (robust)
armor_encode // Unified decode (auto-detects mode)
smart_decode // Cover image optimizer (preprocessing before JPEG compression)
optimize_cover // Capacity estimation with Brotli compression
compressed_payload_size // Image dimension validation
validate_encode_dimensions // Real-time progress tracking
init // reset and set total steps
advance // increment step (capped at total)
finish // mark complete (step = total)
get // read (step, total)
cancel // request cancellation
check_cancelled // returns Err(Cancelled) if set
Types
PayloadData— decoded message text + optional file attachmentsFileEntry— file attachment (filename + content bytes)ShadowLayer— shadow message for plausible deniability (message + passphrase + optional files)EncodeQuality— encode-time score (0-100), contextual hint key, mode (stealth for Ghost, robustness for Armor)DecodeQuality— signal integrity percentage, RS error count/capacity, fortress flagArmorCapacityInfo— capacity breakdown by encoding tier (Phase 1/2/3, Fortress)OptimizerConfig— optimizer settings (strength, seed, mode)OptimizerMode— pipeline variant (Ghost, Armor, Fortress)
The SI-UNIWARD functions (ghost_encode_si, ghost_encode_si_with_files) accept additional parameters for the raw uncompressed pixels (raw_rgb: &[u8], pixel_width: u32, pixel_height: u32). The decoder does not need side information — ghost_decode and smart_decode work for both standard and SI-UNIWARD encoded images.
Video steganography (in development)
Early implementation of H.264 video steganography lives in this crate.
stego/video/h264_pipeline.rs embeds encrypted messages in CAVLC-coded
Baseline H.264 streams across four domains (trailing-one signs,
level-suffix magnitude LSBs, level-suffix sign LSBs, motion-vector
difference LSBs). The pure-Rust H.264 encoder and its CABAC entropy
coder are clean-room — no ffmpeg/x264/OpenH264 code derivation; numeric
tables are transcribed from ITU-T H.264 or cited to published academic
sources.
Source-only distribution (patent-pool posture)
The encoder is gated behind the h264-encoder Cargo feature, which is
OFF by default. Pre-built binaries published to GitHub Releases (the
phasm CLI installer at the bottom of this README) do NOT bundle
the H.264 encoder — they ship JPEG steganography only. Consumers who
want CLI video stego must rebuild from source themselves:
This posture accommodates Via LA's AVC patent-pool licensing for distributed H.264 encoder binaries. Source-only distribution is permitted royalty-free; binary distribution above the free-tier (100,000 units/year as of 2026) is not. Mobile-app builds include the encoder and are subject to the patent-pool terms applicable to those distribution channels — not to source consumers of this crate.
The core/cli/Cargo.toml and core/Cargo.toml make this gating
explicit; do not enable h264-encoder for any binary you intend to
publish through GitHub Releases or other free-tier-blind channels
without first reviewing the Via LA AVC license terms.
Not yet shipped in any phasm CLI or mobile-app build at production quality. The API and default feature set will change. Published here for transparency and review; production use is not recommended at this stage.
Cargo Features
| Feature | Description |
|---|---|
parallel |
Enables Rayon parallelism for J-UNIWARD cost computation, STC embedding, and Armor decode sweeps. Recommended for native builds. |
wasm |
WASM bridge support via wasm-bindgen + js-sys. |
CLI
A command-line interface is available in the cli/ directory.
Install
# macOS / Linux
|
# Windows PowerShell
# Or build from source
Usage
# Encode (Armor mode, default)
# Encode (Ghost mode)
# PNG input → auto Deep Cover (SI-UNIWARD)
# Shadow messages (plausible deniability)
# File attachments
# Decode (auto-detects mode)
# Extract file attachments
# Show capacity
# Pipe message from stdin
|
# Machine-readable output
See phasm encode --help, phasm decode --help, phasm capacity --help for all options.
Building & Testing
Examples
# Encode and decode a message
# Timing benchmark
# Quick decode test
Architecture
Design Principles
- Zero C FFI — pure Rust from JPEG parsing to AES encryption, compiles to native and WASM
- Deterministic — identical output across x86, ARM, and WASM using a pure-IEEE 754 polynomial math module (no
f64::sin/cos— they compile to non-deterministicMath.*in WASM) - Memory-efficient — strip-based wavelet computation, compact positions (8 bytes each), 1-bit packed Viterbi back pointers (32× reduction), segmented checkpoint for large images (O(√n) memory),
i8-quantized SI-UNIWARD rounding errors (8× smaller thanf64), strip-by-strip luma block computation, strategicdrop()calls before JPEG output. Supports 200 MP images under ~1 GB peak memory. - Short messages — optimized for text payloads under 1 KB
- Stego output is raw — the JPEG bytes after encoding must be saved/shared without re-encoding
JPEG Codec
The jpeg module is a from-scratch JPEG coefficient codec — zero dependencies beyond std. It parses JPEG files into DCT coefficient grids, allows modification, and writes them back with byte-for-byte round-trip fidelity. Supports both baseline (SOF0) and progressive (SOF2) JPEG input, always outputs baseline.
Original Huffman tables are preserved from the cover image (with fallback to rebuild if needed).
Cryptography
All payloads are encrypted before embedding:
- Key derivation: Argon2id (RFC 9106) — two tiers:
- Structural key (deterministic from passphrase + fixed salt): drives coefficient permutation and STC matrix generation
- Encryption key (passphrase + random salt): AES-256-GCM-SIV (RFC 8452) with nonce-misuse resistance
- PRNG: ChaCha20 for all key-derived randomness (permutations, spreading vectors, template peaks)
- Payload compression: Brotli (RFC 7932) for compact payloads; flags byte indicates compression
Ghost Pipeline
- Parse JPEG into DCT coefficients
- Derive structural key (Argon2id, overlapped with step 3 on multi-core) → permutation seed + STC seed
- Compute J-UNIWARD costs strip-by-strip (Daubechies-8 wavelet, 3 subbands, parallel row+column filtering) — positions collected inline, no full CostMap materialized
- (SI-UNIWARD only) Modulate costs inline using quantized rounding errors (
i8, <2% precision loss):cost *= (1 - 2|error|). Luma blocks computed in strips of 50 block-rows to minimize peak memory. - Permute coefficient order (Fisher-Yates with ChaCha20)
- Encrypt payload (AES-256-GCM-SIV) and frame (length + CRC)
- Short STC with dynamic w: embed only the actual
mmessage bits (not zero-padded tom_max). The encoder picksw = min(⌊n/m⌋, 10)— for small messages this means 2,500× fewer coefficient modifications compared to fixedw. The decoder brute-forces allwvalues 1–10 in parallel (rayon::find_map_first); CRC32 in the frame format provides instant validation (~0 false positive rate at 2⁻³²). - (Shadow mode) Assign
f32::INFINITYcost to shadow positions → Viterbi routes around them - Embed via STC (h=7) minimizing weighted distortion; SI-UNIWARD uses informed modification direction (toward pre-quantization value)
- Write modified coefficients back to JPEG (with progress reporting per MCU row)
STC Viterbi Optimizer
The STC encoder uses a Viterbi-style dynamic programming algorithm to find the minimum-cost modification sequence across a trellis with 2^h = 128 states. Two key memory optimizations make it practical for large images (32MP+ photos, 48MP camera sensors):
1-bit packed back pointers — The standard Viterbi stores one predecessor state (u32) per trellis state per cover step. Since there are exactly 2 candidate predecessors per target state (stego_bit = 0 or 1), only 1 bit is needed. One u128 (16 bytes) stores all 128 states' decisions per step, replacing a Vec<u32> of 128 entries (512 bytes). 32× memory reduction.
The forward pass iterates over target states instead of source states:
for s in 0..num_states
Traceback reconstructs predecessors from the single bit:
let bit = as u8;
if bit == 1 // undo XOR to get predecessor
At message-block boundaries (shift steps), the pre-shift state is fully determined from the known message bit — no storage needed:
s = & ; // invertible shift
Segmented checkpoint Viterbi — For images with >1M usable positions, even 16 bytes/step can exceed mobile memory (e.g., 100M positions × 16 bytes = 1.6 GB). The segmented approach reduces memory to O(√n):
- Phase A (forward scan): Run the full Viterbi without storing back pointers. Save cost array checkpoints every K = ⌈√m⌉ message blocks (~1 KB each). Total checkpoint memory: ~1.5 MB for maximum payload.
- Phase B (segment recomputation): Process segments in reverse order. For each segment, re-run the forward pass from its checkpoint storing back pointers for that segment only, then traceback and free. Peak memory: ~200 KB per segment.
The dispatcher auto-selects the optimal path:
const SEGMENTED_THRESHOLD: usize = 1_000_000;
if n_used <= SEGMENTED_THRESHOLD else
Both paths produce bit-for-bit identical output (verified by equivalence tests). The segmented path trades 2× compute time for O(√n) memory — typically ~3 MB total for any image size.
| Image | n_used | Inline memory | Segmented memory |
|---|---|---|---|
| 12 MP phone | 2M | 32 MB | 3 MB |
| 32 MB PNG | 5M | 80 MB | 3 MB |
| 48 MP camera | 10M | 160 MB | 3 MB |
| 100 MP medium format | 30M | 480 MB | 3 MB |
| 200 MP flagship | 59M | 944 MB | 3 MB |
Strip-Based UNIWARD & Compact Positions
The J-UNIWARD cost function requires wavelet decomposition of the entire decompressed image — three directional subbands (LH, HL, HH) via Daubechies-8 filters. For a 200 MP image, storing the full pixel array + 3 wavelet subbands would need ~3.2 GB.
The strip-based streaming approach eliminates this:
- Process the image in horizontal strips of 50 block rows (~400 pixels)
- Each strip decompresses its pixel rows (with ±22px padding for the 16-tap wavelet filter boundary), computes row/column-filtered wavelet subbands, and evaluates per-coefficient costs
- Embeddable positions are collected inline into a compact
Vec<CoeffPos>— no full CostMap is ever materialized - Strip memory is freed before the next strip begins
The CoeffPos type uses compact representation: u32 flat index + f32 cost = 8 bytes per position (down from 16 bytes with usize + f64). The STC accepts f32 costs directly, promoting to f64 only for internal Viterbi accumulation.
| Image | DctGrid | Positions | Strip buffers | Total peak |
|---|---|---|---|---|
| 12 MP phone | 24 MB | 48 MB | 12 MB | 84 MB |
| 48 MP camera | 96 MB | 190 MB | 30 MB | 316 MB |
| 100 MP medium format | 200 MB | 400 MB | 50 MB | 650 MB |
| 200 MP flagship | 400 MB | 800 MB | 170 MB | ~1 GB |
Capacity estimation is instantaneous: it counts non-zero AC coefficients directly from the DctGrid (no wavelet computation needed), since J-UNIWARD assigns finite costs to all non-zero coefficients.
Armor Pipeline
- Parse JPEG, derive structural keys
- Compute stability map (select low-frequency AC coefficients)
- Encrypt and frame payload with adaptive RS parity
- Embed via STDM with spreading vectors (ChaCha20-derived)
- For short messages: activate Fortress (BA-QIM on block averages with Watson masking)
- Embed DFT magnitude template for geometric resilience
- Decode: three-phase parallel sweep with soft-decision concatenated codes
Progress Reporting
Both encode and decode pipelines report real-time progress via global atomics (progress::advance()). This enables responsive UI feedback on all platforms:
- Ghost encode: 177 steps — 5 (parse) + 100 (UNIWARD sub-steps) + 50 (STC Viterbi sub-steps) + 2 (permute + key derivation) + 20 (JPEG write MCU rows)
- Ghost encode with shadows: 277 steps — adds 100 (verification UNIWARD pass) for stego-cost check
- Ghost decode: 107 steps — 5 (parse) + 100 (UNIWARD) + 2 (STC extraction + decrypt). Dynamic w brute-force runs in parallel.
- Armor encode: 6 steps (STDM path) or 3 steps (Fortress path, auto-adjusted at runtime)
- Armor decode: dynamic total based on candidate count (~50 steps). Phase 1 delta sweep parallelized across ~21 candidates.
Cancellation is cooperative: progress::cancel() sets a flag checked at natural loop boundaries via progress::check_cancelled().
FFT
In-house Cooley-Tukey + Bluestein FFT implementation using deterministic twiddle factors (det_sincos()). No external FFT crate — guarantees bit-identical results across all platforms.
Project Structure
src/
lib.rs Public API re-exports
det_math.rs Deterministic math (sin/cos/atan2/hypot/exp/powi)
jpeg/
mod.rs JpegImage: parse, modify, serialize
bitio.rs Bit-level reader/writer with JPEG byte stuffing
dct.rs DCT coefficient grids and quantization tables
error.rs JPEG parsing errors
frame.rs SOF frame info (dimensions, components, subsampling)
huffman.rs Huffman coding tables (two-level decode, encode)
marker.rs JPEG marker iterator
pixels.rs IDCT/DCT for pixel-domain operations (forward DCT, luma extraction)
scan.rs Entropy-coded scan reader/writer
tables.rs DQT/DHT table parsing
zigzag.rs Zigzag scan order mapping
stego/
mod.rs Ghost/Armor encode/decode entry points
pipeline.rs Ghost mode pipeline (J-UNIWARD + STC)
crypto.rs AES-256-GCM-SIV + Argon2id key derivation
frame.rs Payload framing (length, CRC, mode byte, salt, nonce)
payload.rs Payload serialization (Brotli, file attachments)
permute.rs Fisher-Yates coefficient permutation
side_info.rs SI-UNIWARD: rounding errors, cost modulation, direction selection
shadow.rs Shadow messages (Y-channel direct LSB + RS ECC, plausible deniability)
optimizer.rs Cover image optimizer (texture-adaptive preprocessing, do-no-harm)
quality.rs Encode quality scoring (stealth for Ghost, robustness for Armor)
capacity.rs Ghost capacity estimation
progress.rs Real-time progress tracking (atomics + WASM callback)
error.rs StegoError enum
cost/
mod.rs Cost function trait
uniward.rs J-UNIWARD (Daubechies-8 wavelet, 3 subbands)
uerd.rs UERD cost function (legacy)
stc/
mod.rs Syndrome-Trellis Codes
embed.rs STC Viterbi embedding (1-bit packed + segmented checkpoint)
extract.rs STC extraction (syndrome computation)
hhat.rs H-hat submatrix generation (ChaCha20-derived)
armor/
mod.rs Armor mode re-exports
pipeline.rs Armor encode/decode (STDM + Fortress + DFT template)
embedding.rs STDM embed/extract with adaptive delta
selection.rs Coefficient stability map
spreading.rs ChaCha20-derived spreading vectors
ecc.rs Reed-Solomon GF(2^8) encoder/decoder
repetition.rs Repetition coding with soft majority voting
capacity.rs Armor capacity estimation
fortress.rs BA-QIM block-average embedding + Watson masking
template.rs DFT magnitude template (geometric resilience)
dft_payload.rs DFT ring-based payload embedding
fft2d.rs 2D FFT (Cooley-Tukey + Bluestein)
resample.rs Bilinear resampling for geometric correction
cli/ Command-line interface (phasm binary)
test-vectors/ Synthetic JPEG test images
tests/ Integration tests (round-trip, cross-platform, geometry)
examples/ CLI tools (encode/decode, timing, diagnostics)
Research & Publications
The algorithms in phasm-core are documented in detail on the Phasm blog:
Steganography Fundamentals
- Adaptive Steganography at Low Embedding Rates: UERD vs J-UNIWARD Detection Benchmarks
- Syndrome-Trellis Codes for Practical JPEG Steganography
- Surviving JPEG Recompression: A Quantitative Analysis of DCT-Domain Robust Steganography
Robustness & Error Correction
- Three Invariants of JPEG Recompression: Block Averages, Brightness Ordering, and Coefficient Signs
- Soft Decoding for Steganography: How LLRs and Concatenated Codes Turn 19% BER Into Zero Errors
- Perceptual Masking Meets QIM: Adaptive Embedding Strength for Invisible Robust Steganography
Geometric Resilience
Plausible Deniability
Implementation
- Building a Pure-Rust JPEG Coefficient Codec
- When f64::sin() Breaks Your Crypto: Building Deterministic Math for WASM Steganography
- From 480 MB to 3 MB: Fitting Viterbi Steganography on a Phone
Platform Analysis
How Does Phasm Compare?
| Feature | Phasm | OpenStego | Steghide | F5 |
|---|---|---|---|---|
| Status | Active (2025–present) | Unmaintained | Unmaintained (2003) | Unmaintained |
| Language | Rust (native + WASM) | Java | C++ | Java |
| Encryption | AES-256-GCM-SIV + Argon2id | DES | Blowfish / AES | None |
| Embedding | J-UNIWARD + STC (h=7) | LSB | LSB-adjacent | DCT histogram shift |
| Steganalysis resistance | High (resists SRNet, XedroudjNet) | Low | Low | Moderate |
| Survives recompression | Yes (Armor / Fortress) | No | No | No |
| Survives WhatsApp | Yes (Fortress, short messages) | No | No | No |
| Plausible deniability | Yes (shadow messages) | No | No | No |
| Side-informed mode | Yes (SI-UNIWARD, +43% capacity) | No | No | No |
| File attachments | Yes (Ghost, up to 2 MB) | No | Yes | No |
| Geometric resilience | Yes (DFT template) | No | No | No |
| Platforms | iOS, Android, Web (WASM), CLI | Desktop (Java) | Linux / Windows CLI | Java |
| Web (no install) | Yes (WebAssembly) | No | No | No |
| Image format | JPEG (any input auto-converted) | PNG, BMP | JPEG, BMP, WAV, AU | JPEG |
| Open source | GPL-3.0 | GPL-2.0 | GPL-2.0 | MIT |
FAQ
Does Phasm work with WhatsApp? Yes. Fortress sub-mode (auto-activates for short messages in Armor mode) survives WhatsApp's default recompression pipeline. For longer messages, send the image as a document/file to bypass compression entirely.
How detectable is Ghost mode? At typical embedding rates (0.4 bpnzAC), even deep-learning steganalysis detectors like SRNet perform near random chance against J-UNIWARD-embedded images. SI-UNIWARD (auto-activated for non-JPEG inputs) further reduces detection risk.
What image sizes are supported? Minimum 200×200 pixels, maximum 8192×8192 / 16 megapixels. Oversized images are automatically downsampled. The engine handles images up to 200 MP internally with ~3 MB peak memory for STC embedding.
Is the output JPEG or PNG? Always JPEG. Input can be any common format (JPEG, PNG, WebP, HEIC) — non-JPEG inputs are auto-converted and enable SI-UNIWARD Deep Cover in Ghost mode.
Can I hide files, not just text? Yes, in Ghost mode. File attachments up to 2 MB per file are supported alongside text messages. Files are Brotli-compressed before embedding.
What happens if I lose my passphrase? The message cannot be recovered. Phasm uses AES-256-GCM-SIV with Argon2id key derivation — no backdoor, no recovery mechanism. Only the correct passphrase can decrypt the message.
Can someone prove a message exists without the passphrase? Ghost mode is designed to resist this. Shadow messages add plausible deniability — you can reveal a decoy passphrase that decodes a harmless message, with no way to prove additional messages exist.
Does Phasm work offline? Yes. All processing happens on-device. The web version runs entirely in WebAssembly — no server communication, no account required.
License
GPL-3.0-only. See LICENSE.
Third-party dependency licenses are listed in THIRD_PARTY_LICENSES.