jdvrif(JPG Data Vehicle)是一款快速、易用的隐写术命令行工具,可通过 JPG 图像隐藏和提取任意文件类型。仅限 Linux。
你的数据文件会先用 libdeflate/zlib 压缩,再用 XChaCha20-Poly1305(libsodium secretstream)加密,密钥由 Argon2id 从随机生成的 恢复 PIN 派生,最后嵌入到载体图像中。PIN 仅在 conceal 结束时显示一次,且不会存储在任何地方:没有它就无法恢复被隐藏的文件。
使用 默认隐藏模式,你可以隐藏任意文件类型,生成的图像上限为 2GiB。其他平台的隐藏模式以及兼容的社交媒体网站(如下所列)有各自小得多的大小限制和其他要求。
jdvrif 还有一个网页版,你可以立即使用,作为下载并编译 CLI 源代码的便捷替代方案。网页文件上传限制为 20MiB。
对于对该语言感兴趣的用户,还提供了一个实验性的 Rust 移植版 jdvrif-rs。它在格式上兼容:任一构建都能恢复对方生成的图像。
jdvrif 部分源自安全研究员 David Buchanan 所***实现的技术***。

演示图像:"A place of concealment" / PIN: 7912872750612437051
与常见的 LSB(最低有效位)隐写术方法(将数据隐藏在载体图像的像素中)不同,jdvrif 主要将数据隐藏在 JPG 图像的应用段(ICC、EXIF、XMP 等)中。
| 隐藏模式 | 数据存放位置 | 可在以下平台分享输出图像 |
|---|---|---|
| (无选项) | APP2/ICC 配置文件段 | X-Twitter、Tumblr、Mastodon、Pixelfed、PostImage、ImgBB、ImgPile、Flickr |
| -b | 带框架的 EXIF / Photoshop / XMP 段 | 仅 Bluesky |
| -r | JPEG DCT 系数(QIM) | 仅 Reddit |
| -x | JPEG DCT 系数(J-UNIWARD/STC) | 仅 X-Twitter |
默认段存储方法有两个平台例外:Reddit 和 X-Twitter。两者都有各自的隐藏模式,且这两种模式都完全不使用元数据段:-r 和 -x 改为将载荷承载在 JPG 图像的 DCT 系数中。
Reddit 会重新编码上传的图像,并丢弃默认模式所依赖的元数据段,因此 -r 是唯一在该平台有效的模式。X-Twitter 确实会保留单个小型 ICC 段,因此默认模式在该平台上仍然有效,但仅适用于极小的载荷。
对于 Reddit 隐藏模式(-r),我们使用 QIM 隐写术方法(JPEG DCT 域量化索引调制),因为这是目前唯一适用于 Reddit 的存储方法。载体图像会被转码为 baseline Q75 4:2:0,载荷承载在其亮度 DCT 块中。
为了最大化 Reddit 平台的存储容量,请使用尺寸较大的载体图像,例如 2048x2048、4096x4096、8192x8192(最大) 等。
对于此方法,载体图像的质量并不重要,对于最大尺寸应保持基础质量,以帮助最小化载体图像的文件大小。
虽然 X-Twitter 平台可以使用 jdvrif 提供的默认方法(数据隐藏在 APP2/ICC 段中),但 X-Twitter 将其限制为单个 ICC 段,最大大小仅为 ~10KiB。
要承载超过 ~10KiB 的数据,请使用 X-Twitter 平台隐藏模式(-x)。该模式完全放弃元数据段——不会向 ICC 配置文件写入任何内容——转而使用带网格编码(STC)的自适应 J-UNIWARD 隐写方法。载体图像会被转码为渐进式 4:2:0,并采用其源派生质量(上限为 Q97)。
要最大化 X-Twitter 平台的存储容量,请使用高质量/细节丰富且尺寸较大的载体图像,如 1024x1024、2048x2048、4096x4096(最大) 等。
-r 和 -x 载体使用 Argon2id 加密你的数据,并将数据位置与你的恢复 PIN 绑定,但嵌入操作仍会改变图像的 DCT 统计特征,因此专用的隐写分析可能揭示图像携带了隐藏数据。
使用 jdvrif v9.0 创建的 -r 和 -x 图像仍可恢复,但 v9.0 无法恢复使用 v9.1 创建的 -r 或 -x 图像。
两种 DCT 模式承载的数据量都远少于默认模式,因此在选择载荷之前,请使用 capsize 测量载体图像(参见检查容量)。
构建需要 CMake 3.20 或更新版本、来自 util-linux 的 flock,以及 Ninja(首选)或 Make。编译器必须为 GCC 14 或更新版本,或 Clang 18 或更新版本,并搭配实现了 std::format 和 std::print 等特性的 C++23 标准库。所需的原生库包括 libsodium、libjpeg-turbo(同时需要 turbojpeg 和 libjpeg 两套 API)、zlib 和 libdeflate。OpenMP 为可选项:若存在,它会并行化 -x 的 J-UNIWARD 代价计算过程。
$ 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
如果你的发行版将 GCC 14 作为带版本号的软件包提供,请安装 g++-14 并使用 CXX=g++-14 ./compile_jdvrif.sh 进行构建。
该封装脚本将依赖跟踪的目标文件保存在 src/build/ 下,因此后续调用只会重新构建发生变化的部分,并且只有在完整、成功构建之后才会替换已发布的 jdvrif 二进制文件。设置 JDVRIF_JOBS=<count> 可更改并行度限制(默认值:CPU 数量,上限为 8),设置 JDVRIF_BUILD_DIR=<path> 可使用单独的构建缓存,或设置 BUILD_MODE=sanitize 以进行 ASan/UBSan 构建。
$ jdvrif
Usage: jdvrif conceal [-b|-r|-x] <cover_image> <secret_file>
jdvrif recover <cover_image>
jdvrif capsize [-r|-x] <cover_image>
jdvrif --info
运行 jdvrif --info 可查看关于模式、平台选项和大小限制的完整内置指南。
$ 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 参数:
conceal - 压缩、加密并将你的秘密数据文件嵌入到 JPG 封面图像中。
recover - 解密、解压缩并从 JPG 封面图像中提取隐藏的数据文件(需要恢复 PIN)。
capsize - 报告封面图像在 -r 或 -x 模式下的载体容量。不会保存任何图像。
封面图像的要求:
● 仅限 JPEG,至少 400x400 像素,且为灰度或 YCbCr 色彩——CMYK/YCCK 图像必须先转换为 RGB。
● 默认和 -b 的上限尺寸为 4096x4096px;-r 使用 8192x8192px,而 -x 也限制为 4096x4096px。
● 默认模式(无选项)还会拒绝估计 JPEG 质量高于 Q97 的封面图像;如果图像被拒绝,请以较低质量重新保存。
秘密数据文件的要求:
● 嵌入的文件名不得超过 20 字节,不得以 . 或 - 开头,也不得以空格或 . 结尾,并且必须是有效的 UTF-8,不含控制字符或双向格式字符,也不含 / \ : * ? " < > | 中的任何字符。该名称存储在图像中,并在 recover 时恢复。
● 你的数据文件在加密前会被压缩,但已识别的已压缩文件类型(.zip、.7z、.mp4、.jpg、.png 等)除外。在默认和 -b 模式下,这些类型仅在大于 10MiB 时才跳过压缩;在 -r 和 -x 模式下,它们始终跳过压缩。对于其他任何要用于小型平台限制的文件,请考虑先自行压缩(zip、rar、7z 等),以便你确切知道其存储大小。
******************
注意:Bluesky 现在默认将图像保存为 WEBP。
要将图像保存为 JPG,以便你仍能使用 jdvrif 恢复隐藏数据:-
首先点击帖子中的图像将其打开,然后右键点击该图像。从菜单中选择 在新标签页中打开图像。
选择新标签页,在地址栏中,将光标移到地址末尾并添加 @jpg,然后按回车。
右键点击图像,从菜单中选择 保存图像...
你的图像现在应已下载为 JPG,此时即可与 jdvrif 配合使用。
如果你想使用 WEBP 图像隐藏数据并发布到 Bluesky,可以使用我的 WEBP 隐写命令行工具 wbpdv
******************
发布大小限制按 封面图像 + 压缩数据文件 的 合计 大小计算:
● Flickr(200MiB)、ImgPile(100MiB)、ImgBB(32MiB)、PostImage(32MiB)、Pixelfed(15MiB)。
大小限制仅按 压缩数据文件大小 计算:
● Mastodon(~6MiB)、Tumblr(~64KiB)、X-Twitter(~10KiB / 默认方法)。
例如,对于 Mastodon,如果你的封面图像为 1MiB,你仍然可以嵌入最大至 ~6MiB 大小限制的数据文件。
其他:拥有自己隐藏模式的平台:
● Bluesky(-b 选项)。完成的“已嵌入文件”JPG 不得超过 2,000,000 字节(~1.9MiB),因此封面图像和压缩数据文件共享同一预算。压缩数据文件本身不得超过 ~171KiB。已经达到 2,000,000 字节的封面图像完全没有剩余空间,因此请让封面图像比限制至少小出你的压缩数据文件的大小。需要“create_bsky_post.py”脚本才能在 Bluesky 上发布这些图像。有关此脚本的更多信息,请见本页下方。
● Reddit(-r 选项)。封面图像和数据文件各自不得大于 20MiB,但封面图像的实际载体容量要小得多,并且取决于其尺寸大小。使用 jdvrif capsize -r 来测量它。
● X-Twitter(-x 选项)。封面图像和数据文件各自不得大于 5MiB,且封面不得超过 4096x4096 像素。实际载体容量要小得多,取决于图像质量和尺寸大小。使用 jdvrif capsize -x 来测量它。
在默认模式和 -b 模式下,封面图像在使用前还会进行无损优化,优化后不得超过 4MiB。
对于 X-Twitter、Reddit 和 Tumblr 等数据大小限制较小的平台,你可能需要专注于压缩效果好的数据,例如文本文件等。
https://github.com/user-attachments/assets/af330d09-f65d-4f75-8290-883078be34b3
https://github.com/user-attachments/assets/fc454d42-0240-4864-b44b-ce5ef7cfd94c
capsize 会完全按照 conceal 的方式准备封面图像,然后报告它可以承载多少加密载荷。不会向磁盘写入任何内容。使用 -r 指定 Reddit 载体,使用 -x 指定 X-Twitter 载体(如果未给出任何选项,-x 为默认值)。
$ jdvrif capsize -r basic_img_large_dims.jpg
Reddit capacity check for conceal -r mode only.
Cover Image: 384KiB, 8192x8192, Baseline YCbCr 4:2:0, Standard Q75 quantization (C3).