
Steganography Tool for JPG Images
jdvrif (JPG Data Vehicle, v9.0) is a fast, easy-to-use steganography command-line tool for concealing and extracting any file type via a JPG image. Linux only.
Your data file is compressed with libdeflate/zlib, then encrypted with XChaCha20-Poly1305 (libsodium secretstream) under a key derived by Argon2id from a randomly generated recovery PIN, and finally embedded in the cover image. The PIN is displayed once, at the end of conceal, and is never stored anywhere: without it the concealed file cannot be recovered.
Using the default conceal mode, you can conceal any file type up to 2GiB. The other platform conceal modes and the compatible social media sites (listed below) have their own much smaller size limits and other requirements.
There is a Web edition of jdvrif, which you can use immediately, as a convenient alternative to downloading and compiling the CLI source code. Web file uploads are limited to 20MiB.
An experimental Rust port jdvrif-rs is also available for those interested in that language. It is format-compatible: either build can recover the other's images.
jdvrif partly derives from the technique implemented by security researcher David Buchanan.

Demo Image: "A place of concealment" / PIN: 2190398302048725932
Unlike the common LSB (Least Significant Bit) steganography method of concealing data within the pixels of a cover image, jdvrif mostly hides data within application segments of a JPG image (ICC, EXIF, XMP, etc).
| Conceal mode | Where the data goes | Share the output image on |
|---|---|---|
| (no option) | APP2/ICC profile segments | X-Twitter, Tumblr, Mastodon, Pixelfed, PostImage, ImgBB, ImgPile, Flickr |
| -b | framed EXIF / Photoshop / XMP segments | Bluesky only |
| -r | JPEG DCT coefficients (QIM) | Reddit only |
| -x | JPEG DCT coefficients (J-UNIWARD/STC) | X-Twitter only |
The two platform exceptions to the default segment storage method are Reddit and X-Twitter. Both have their own conceal mode, and neither of those modes uses metadata segments at all: -r and -x carry the payload in the JPG image's DCT coefficients instead.
Reddit re-encodes uploaded images and discards the metadata segments the default mode relies on, so -r is the only mode that works there. X-Twitter does preserve a single, small ICC segment, so the default mode still works on that platform, but only for a tiny payload.
For the Reddit conceal mode (-r), we use the QIM steganography method (JPEG DCT-domain Quantization Index Modulation), as this is the only storage method that currently works for Reddit. The cover is transcoded to baseline Q75 4:2:0 and the payload is carried in its luminance DCT blocks.
To maximise storage capacity for the Reddit platform, use a cover image with large dimension sizes, 2048x2048, 4096x4096, 8192x8192 (max), etc.
Quality of cover image is not important for this method and should be kept basic for the largest dimensions to help minimise cover image file size.
While the X-Twitter platform can use the default method provided by jdvrif, where data is concealed within APP2/ICC segments, X-Twitter limits this to a single ICC segment with a maximum size of just ~10KiB.
To carry more than that ~10KiB, use the X-Twitter platform conceal mode (-x). It abandons metadata segments entirely — nothing is written to an ICC profile — and instead uses the adaptive J-UNIWARD steganography method with Syndrome-Trellis Coding (STC). The cover is transcoded to progressive 4:2:0 at its source-derived quality (capped at Q97).
To maximise storage capacity for the X-Twitter platform, use a high quality/detailed cover image with large dimension sizes, 1024x1024, 2048x2048, 4096x4096 (max), etc.
Both DCT modes carry far less data than the default mode, so use capsize to measure a cover image before choosing a payload (see Checking capacity).
Building requires CMake 3.20 or newer, flock from util-linux, and either Ninja (preferred) or Make. The compiler must be GCC 14 or newer, or Clang 18 or newer paired with a C++23 standard library that implements features such as std::format and std::print. The native libraries required are libsodium, libjpeg-turbo (both the turbojpeg and libjpeg APIs), zlib and libdeflate. OpenMP is optional: when present it parallelises the -x J-UNIWARD cost pass.
$ sudo apt update
$ sudo apt install g++ cmake ninja-build util-linux libsodium-dev libturbojpeg0-dev libjpeg-dev zlib1g-dev libdeflate-dev
$ g++ --version # confirm the reported version is 14 or newer
$ chmod +x compile_jdvrif.sh
$ ./compile_jdvrif.sh
$ sudo cp jdvrif /usr/bin
If your distribution ships GCC 14 as a versioned package, install g++-14 and build with CXX=g++-14 ./compile_jdvrif.sh.
The wrapper keeps dependency-tracked object files under src/build/, so later invocations rebuild only what changed, and it replaces the published jdvrif binary only after a complete, successful build. Set JDVRIF_JOBS=<count> to change the parallelism limit (default: CPU count, capped at 8), JDVRIF_BUILD_DIR=<path> to use a separate build cache, or BUILD_MODE=sanitize for an ASan/UBSan build.
$ jdvrif
Usage: jdvrif conceal [-b|-r|-x] <cover_image> <secret_file>
jdvrif recover <cover_image>
jdvrif capsize [-r|-x] <cover_image>
jdvrif --info
Run jdvrif --info for the full built-in guide to modes, platform options and size limits.
$ jdvrif conceal your_cover_image.jpg your_secret_file.doc
Platform compatibility for output image:-
✓ X-Twitter
✓ Tumblr
✓ Mastodon
✓ Pixelfed
✓ PostImage
✓ ImgBB
✓ ImgPile
✓ Flickr
Recovery PIN: [***2166776980318349924***]
Important: Keep your PIN safe, so that you can extract the hidden file.
Saved "file-embedded" JPG image: jrif_4e87c566c.jpg (143029 bytes).
Complete!
$ jdvrif recover jrif_4e87c566c.jpg
PIN: *******************
Extracted hidden file: your_secret_file.doc (6165 bytes).
Complete! Please check your file.
jdvrif mode arguments:
conceal - Compresses, encrypts and embeds your secret data file within a JPG cover image.
recover - Decrypts, uncompresses and extracts the concealed data file from a JPG cover image (recovery PIN required).
capsize - Reports the carrier capacity of a cover image for -r or -x mode. No image is saved.
Requirements for the cover image:
● JPEG only, at least 400x400 pixels, and either grayscale or YCbCr colour — CMYK/YCCK images must be converted to RGB first.