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.
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).
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.
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.
smart_decode tries all modes automatically — no mode selector needed on the decode side.
use phasm_core::{ghost_encode, ghost_decode};
let cover = std::fs::read("photo.jpg").unwrap();
let stego = ghost_encode(&cover, "secret message", "passphrase").unwrap();
let decoded = ghost_decode(&stego, "passphrase").unwrap();
assert_eq!(decoded.text, "secret message");use phasm_core::{armor_encode, armor_decode};
let cover = std::fs::read("photo.jpg").unwrap();
let stego = armor_encode(&cover, "secret message", "passphrase").unwrap();
let (decoded, quality) = armor_decode(&stego, "passphrase").unwrap();
assert_eq!(decoded.text, "secret message");
println!("Integrity: {:.0}%", quality.integrity_percent);// Ghost mode (stealth)
ghost_encode(jpeg_bytes, message, passphrase) -> Result<Vec<u8>>
ghost_decode(jpeg_bytes, passphrase) -> Result<PayloadData>
ghost_encode_with_files(jpeg_bytes, message, files, passphrase) -> Result<Vec<u8>>
ghost_capacity(jpeg_image) -> Result<usize>
// Armor mode (robust)
armor_encode(jpeg_bytes, message, passphrase) -> Result<Vec<u8>>
armor_decode(jpeg_bytes, passphrase) -> Result<(PayloadData, DecodeQuality)>
armor_capacity(jpeg_image) -> Result<usize>
// Unified decode (auto-detects mode)
smart_decode(jpeg_bytes, passphrase) -> Result<(PayloadData, DecodeQuality)>
// Capacity estimation with Brotli compression
compressed_payload_size(text, mode) -> usizePayloadData— decoded message text + optional file attachmentsDecodeQuality— signal integrity percentage, RS error count/capacity, fortress flagArmorCapacityInfo— capacity breakdown by encoding tier (Phase 1/2/3, Fortress)
| 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. |
cargo test -p phasm-core # default (single-threaded)
cargo test -p phasm-core --features parallel # with Rayon parallelism# Encode and decode a message
cargo run -p phasm-core --example test_encode -- photo.jpg "Hello" "passphrase"
cargo run -p phasm-core --example test_encode -- --decode stego.jpg "passphrase"
# Timing benchmark
cargo run -p phasm-core --example test_timing -- stego.jpg
# Quick decode test
cargo run -p phasm-core --example test_link -- stego.jpg- 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 FDLIBM-based math (no
f64::sin/cos— they compile to non-deterministicMath.*in WASM) - 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
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).
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
- Parse JPEG into DCT coefficients
- Derive structural key (Argon2id) -> permutation seed + STC seed
- Compute J-UNIWARD cost map (Daubechies-8 wavelet, 3 subbands)
- Permute coefficient order (Fisher-Yates with ChaCha20)
- Encrypt payload (AES-256-GCM-SIV) and frame (length + CRC)
- Embed via STC (h=7) minimizing weighted distortion
- Write modified coefficients back to JPEG
- 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
In-house Cooley-Tukey + Bluestein FFT implementation using deterministic twiddle factors (det_sincos()). No external FFT crate — guarantees bit-identical results across all platforms.
src/
lib.rs Public API re-exports
det_math.rs Deterministic math (FDLIBM sin/cos/atan2/hypot)
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
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
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
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
capacity.rs Ghost capacity estimation
progress.rs Real-time decode progress (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 embedding (Viterbi forward pass)
extract.rs STC extraction (syndrome computation)
hhat.rs H-hat submatrix generation
armor/
mod.rs Armor mode re-exports
pipeline.rs Armor encode/decode (three-phase parallel sweep)
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
test-vectors/ Synthetic JPEG test images
tests/ Integration tests (round-trip, cross-platform, geometry)
examples/ CLI tools (encode/decode, timing, diagnostics)
The algorithms in phasm-core are documented in detail on the Phasm blog:
- 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
- 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
- Building a Pure-Rust JPEG Coefficient Codec
- When f64::sin() Breaks Your Crypto: Building Deterministic Math for WASM Steganography
GPL-3.0-only. See LICENSE.
Third-party dependency licenses are listed in THIRD_PARTY_LICENSES.