
Инструмент стеганографии для изображений JPG
# jdvrif
***jdvrif*** (*JPG Data Vehicle*) — это быстрый и простой в использовании инструмент стеганографии командной строки для сокрытия и извлечения файлов любого типа через **JPG**-изображение. ***Только для Linux***.
Ваш файл данных сжимается с помощью ***libdeflate/zlib***, затем шифруется с помощью ***XChaCha20-Poly1305*** (secretstream из ***libsodium***) ключом, производным от ***Argon2id*** из случайно сгенерированного ***восстановительного PIN-кода***, и наконец встраивается в изображение-контейнер. PIN-код отображается один раз, в конце работы команды ***conceal***, и нигде не сохраняется: без него скрытый файл невозможно восстановить.
Используя ***режим сокрытия по умолчанию***, вы можете скрыть файл любого типа, при этом итоговое изображение ограничено размером в ***2GiB***. Остальные режимы сокрытия для платформ и совместимые сайты социальных сетей (*перечисленные ниже*) имеют свои собственные ***гораздо меньшие*** ограничения по размеру и другие требования.
Существует [***веб-версия***](https://cleasbycode.co.uk/jdvrif/app/) ***jdvrif***, которую можно использовать сразу же как удобную альтернативу загрузке и компиляции исходного кода CLI. Загрузка файлов через веб-интерфейс ограничена **20MiB**.
Для тех, кто интересуется этим языком, также доступен экспериментальный порт на ***Rust*** [***jdvrif-rs***](https://github.com/CleasbyCode/jdvrif-rs). Он совместим по формату: любая из сборок может восстановить изображения, созданные другой.
***jdvrif*** частично основан на ***[реализованной технике](https://www.vice.com/en/article/bj4wxm/tiny-picture-twitter-complete-works-of-shakespeare-steganography)*** исследователя в области безопасности ***[David Buchanan](https://www.da.vidbuchanan.co.uk/).***

*Демонстрационное изображение: **"A place of concealment"** / ***PIN: 7912872750612437051****
## Как jdvrif скрывает данные
В отличие от распространённого метода стеганографии [***LSB***](https://ctf101.org/forensics/what-is-stegonagraphy/) (*Least Significant Bit*), при котором данные скрываются в пикселях изображения-контейнера, ***jdvrif*** в основном прячет данные внутри ***сегментов приложения*** ***JPG***-изображения (ICC, EXIF, XMP и т. д.).
| Режим сокрытия | Куда помещаются данные | Где можно поделиться итоговым изображением |
| --- | --- | --- |
| *(без опции)* | сегменты профиля APP2/ICC | X-Twitter, Tumblr, Mastodon, Pixelfed, PostImage, ImgBB, ImgPile, Flickr |
| ***-b*** | сегменты EXIF / Photoshop / XMP с обрамлением | только ***Bluesky*** |
| ***-r*** | DCT-коэффициенты JPEG (***QIM***) | только ***Reddit*** |
| ***-x*** | DCT-коэффициенты JPEG (***J-UNIWARD/STC***) | только ***X-Twitter*** |
Два платформенных исключения из метода хранения в сегментах по умолчанию — это ***Reddit*** и ***X-Twitter***. У обеих есть собственный режим сокрытия, и ни один из этих режимов вообще не использует сегменты метаданных: ***-r*** и ***-x*** переносят полезную нагрузку в DCT-коэффициентах ***JPG***-изображения.
***Reddit*** перекодирует загруженные изображения и отбрасывает сегменты метаданных, на которые опирается режим по умолчанию, поэтому ***-r*** — единственный режим, который там работает. ***X-Twitter*** сохраняет один небольшой сегмент ICC, поэтому режим по умолчанию на этой платформе всё ещё работает, но только для крошечной полезной нагрузки.
Для режима сокрытия ***Reddit*** (***-r***) мы используем [***метод стеганографии QIM***](https://www.google.com/search?q=QIM+steganography+method&sourceid=chrome&ie=UTF-8&source=chrome.ctxt) (*Quantization Index Modulation в области DCT-коэффициентов JPEG*), поскольку это единственный метод хранения, который в настоящее время работает для ***Reddit***. Изображение-контейнер перекодируется в baseline Q75 4:2:0, а полезная нагрузка переносится в его блоках DCT яркости.
Чтобы максимизировать ёмкость хранилища для платформы ***Reddit***, используйте изображение-контейнер с большими размерами, **2048x2048**, **4096x4096**, **8192x8192 (макс.)** и т. д.
Качество изображения-контейнера для этого метода не важно, и его следует оставлять базовым для наибольших размеров, чтобы помочь минимизировать размер файла изображения-контейнера.
Хотя платформа ***X-Twitter*** может использовать метод по умолчанию, предоставляемый ***jdvrif***, при котором данные скрываются в сегментах APP2/ICC, ***X-Twitter*** ограничивает это одним сегментом ICC с максимальным размером всего **~10KiB**.
Чтобы вместить больше этих **~10 КиБ**, используйте режим сокрытия платформы ***X-Twitter*** (***-x***). Он полностью отказывается от сегментов метаданных — в ICC-профиль ничего не записывается — и вместо этого использует [***адаптивный метод стеганографии J-UNIWARD с кодированием Syndrome-Trellis (STC)***](https://www.google.com/search?q=adaptive+J-UNIWARD+steganography+method+with+Syndrome-Trellis+Coding+(STC)). Контейнер транскодируется в прогрессивный 4:2:0 с качеством, производным от исходного (с ограничением **Q97**).
Чтобы максимизировать ёмкость хранилища для платформы ***X-Twitter***, используйте контейнерное изображение высокого качества/детализации с большими размерами, **1024x1024**, **2048x2048**, **4096x4096 (макс.)** и т. д.
Носители ***-r*** и ***-x*** шифруют ваши данные и привязывают их местоположение к вашему PIN-коду восстановления с помощью ***Argon2id***, но встраивание всё равно изменяет статистику DCT изображения, поэтому целенаправленный стегоанализ может выявить, что изображение содержит скрытые данные.
Изображения ***-r*** и ***-x***, созданные с помощью ***jdvrif v9.0***, остаются восстановимыми, но ***v9.0*** не может восстановить изображения ***-r*** или ***-x***, созданные с помощью ***v9.1***.
Оба режима DCT переносят гораздо меньше данных, чем режим по умолчанию, поэтому используйте ***capsize*** для измерения контейнерного изображения перед выбором полезной нагрузки (см. [Проверка ёмкости](#checking-capacity-with-capsize)).
## Требования и компиляция (Linux)
Для сборки требуются **CMake 3.20 или новее**, `flock` из **util-linux**, а также **Ninja** (предпочтительно) или **Make**. Компилятор должен быть **GCC 14 или новее** либо **Clang 18 или новее** в паре со стандартной библиотекой C++23, реализующей такие возможности, как `std::format` и `std::print`. Требуемые нативные библиотеки: **libsodium**, **libjpeg-turbo** (*оба API — `turbojpeg` и `libjpeg`*), **zlib** и **libdeflate**. **OpenMP** необязателен: при его наличии он распараллеливает проход вычисления стоимости J-UNIWARD для ***-x***.
```console
$ 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.
## Использование
```console
$ 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`, чтобы получить полное встроенное руководство по режимам, параметрам платформ и ограничениям размера.
```console
$ 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:
***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***, затем нажмите Enter.
Щёлкните по изображению правой кнопкой мыши и в меню выберите ***Сохранить изображение...***
Теперь ваше изображение должно быть загружено как ***JPG***, которое будет работать с ***jdvrif***.
Если вам нужен инструмент для сокрытия данных с использованием изображений ***WEBP*** для публикации в ***Bluesky***, вы можете использовать мой CLI-инструмент стеганографии для ***WEBP*** ***[wbpdv](https://github.com/CleasbyCode/wbpdv)***
\******************
*Ограничение размера при публикации измеряется по ***суммарному*** размеру ***изображения-обложки*** + ***сжатого файла данных:****
● ***Flickr*** (**200 МиБ**), ***ImgPile*** (**100 МиБ**), ***ImgBB*** (**32 МиБ**), ***PostImage*** (**32 МиБ**), ***Pixelfed*** (**15 МиБ**).
*Ограничение размера измеряется ***только*** по ***размеру сжатого файла данных:****
● ***Mastodon*** (**~6MiB**), ***Tumblr*** (**~64KiB**), ***X-Twitter*** (**~10KiB / метод по умолчанию**).
Например, с ***Mastodon***, если ваше изображение-обложка имеет размер **1MiB**, вы всё ещё можете встроить файл данных вплоть до предела **~6MiB**.
**Другое: платформы с собственным режимом сокрытия:**
● ***Bluesky*** (***опция -b***). Готовый «*со встроенным файлом*» ***JPG*** не должен превышать **2 000 000 байт (~1,9MiB)**, поэтому изображение-обложка и сжатый файл данных делят один бюджет. Сам сжатый файл данных не должен превышать **~171KiB**. Изображение-обложка, уже имеющее размер 2 000 000 байт, не оставляет места вообще, поэтому держите обложку меньше предела как минимум на размер вашего сжатого файла данных. Для публикации этих изображений в ***Bluesky*** требуется скрипт «***create_bsky_post.py***». *Подробнее об этом скрипте ниже на странице.*
● ***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
***capsize*** подготавливает обложку точно так же, как это сделал бы ***conceal***, а затем сообщает, какой объём зашифрованных данных она может вместить. На диск ничего не записывается. Используйте ***-r*** для носителя ***Reddit*** и ***-x*** для носителя ***X-Twitter*** (***-x*** используется по умолчанию, если не указана ни одна опция).
```console
$ 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).
Theoretical C3 capacity limit for this cover image: 436906 bytes (~426KiB).
Conservative maximum compressed capacity with a 20-character filename: 436792 bytes (~426KiB).
Recommended maximum compressed capacity with a 20-character filename: 435768 bytes (~425KiB).
```
Указанная цифра — это общая ёмкость зашифрованного ***конверта***, а не необработанный лимит секретного файла: имя файла, метаданные шифрования и восстановления занимают от 95 до 114 байт для полезной нагрузки в одном фрейме, а более крупные полезные нагрузки добавляют накладные расходы на фрейминг. Не стремитесь к теоретическому пределу — где позволяет ёмкость, держите сжатый объём полезной нагрузки как минимум на **1KiB** ниже консервативного максимума. Проверка размера, выполняемая ***conceal***, является авторитетной.
## Платформенные опции режима conceal
Чтобы создавать совместимые "*data-concealed*" изображения ***JPG*** для публикации на платформе ***Reddit***, необходимо использовать опцию ***-r*** с режимом ***conceal***.
```console
$ jdvrif conceal -r my_image.jpg hidden.doc
```
Эти изображения совместимы только для публикации на ***Reddit***. Ваш встроенный файл данных будет потерян при публикации на другой платформе.
При сохранении/скачивании изображения с ***Reddit*** обязательно нажмите на изображение в посте, чтобы полностью развернуть его перед сохранением.
https://github.com/user-attachments/assets/ead3c34c-dd9a-4018-9ea3-0746fbdfdb8b
Чтобы создать совместимые "*data-concealed*" изображения ***JPG*** для публикации на платформе ***X-Twitter*** с использованием метода стеганографии J-UNIWARD, необходимо использовать опцию ***-x*** с режимом ***conceal***.
```console
$ jdvrif conceal -x my_image.jpg hidden.doc
```
Эти изображения совместимы только для публикации в ***X-Twitter***. Ваш файл со встроенными данными будет утерян при публикации на другой платформе.
При сохранении/скачивании изображения из ***X-Twitter*** обязательно нажмите на изображение в посте, чтобы полностью развернуть его перед сохранением.
Чтобы создать совместимые "*file-embedded*" изображения ***JPG*** для публикации на платформе ***Bluesky***, необходимо использовать опцию ***-b*** с режимом ***conceal***.
```console
$ jdvrif conceal -b my_image.jpg hidden.doc
```
Эти изображения совместимы только для публикации в ***Bluesky***. Ваш встроенный файл данных будет удалён, если опубликовать его на другой платформе.
Также необходимо использовать скрипт Python [create_bsky_post.py](https://github.com/CleasbyCode/jdvrif/blob/main/src/bsky/create_bsky_post.py) (находится в папке ***src/bsky*** репозитория) для публикации изображения в ***Bluesky***.
Это не сработает, если вы публикуете изображения в ***Bluesky*** через браузерный сайт или мобильное приложение.
Чтобы использовать скрипт, вам нужно создать [***app password***](https://bsky.app/settings/app-passwords) в вашей учётной записи ***Bluesky***. Передавайте свои учётные данные через переменные окружения, а не в командной строке, где они были бы видны другим локальным пользователям с помощью таких инструментов, как `ps`:
```console
$ pip install -r bsky/requirements.txt
$ export ATP_AUTH_HANDLE='you.bsky.social'
$ read -rsp 'Bluesky app password: ' ATP_AUTH_PASSWORD && export ATP_AUTH_PASSWORD
$ python3 bsky/create_bsky_post.py \
--image jrif_4e87c566c.jpg \
--alt-text "alt-text here [optional]" \
"standard post text here [required]"
$ unset ATP_AUTH_PASSWORD
```
Полный набор параметров (*несколько изображений, ответы, цитируемые посты, карточки ссылок*) и описание того, от чего защищает усиленный форк скрипта, см. в [src/bsky/README.md](https://github.com/CleasbyCode/jdvrif/blob/main/src/bsky/README.md).
https://github.com/user-attachments/assets/1daef508-d304-491f-bfe2-2cdbb5d62081
## Тесты
Каждый тестовый скрипт в `src/tests` принимает в качестве аргумента бинарный файл для проверки:
```console
$ bash tests/run_golden_tests.sh --bin ./jdvrif # recover pre-built images with known PINs
$ bash tests/run_roundtrip_tests.sh --bin ./jdvrif # fresh conceal/recover round-trips
$ bash tests/run_security_smoke.sh --bin ./jdvrif # negative-path and boundary checks
$ bash tests/run_reddit_tests.sh --bin ./jdvrif # -r QIM carrier
$ bash tests/run_twitter_tests.sh --bin ./jdvrif # -x J-UNIWARD/STC carrier
$ bash tests/run_io_security_tests.sh --bin ./jdvrif # input-size bounds, PIN signal/job-control handling
$ bash tests/run_jpeg_safety_tests.sh # JPEG parser/decoder safety (builds its own test binary)
$ bash tests/run_bsky_tests.sh # Bluesky posting helper
```
## Стороннее программное обеспечение и ресурсы
### Основные приложения
- [libsodium](https://github.com/jedisct1/libsodium) — криптографическая генерация случайных чисел, вывод ключей Argon2id
и секретные потоки XChaCha20-Poly1305. Динамически компонуется как системная библиотека.
Лицензия: [ISC License](https://github.com/jedisct1/libsodium/blob/master/LICENSE)
Copyright (c) 2013–2026 Frank Denis.
- [libjpeg-turbo](https://github.com/libjpeg-turbo/libjpeg-turbo) — обработка JPEG и преобразование без потерь. Динамически компонуется как системная библиотека.
Это программное обеспечение частично основано на работе Independent JPEG Group.
Лицензии: [Independent JPEG Group License, Modified BSD 3-Clause License,
и zlib License](https://github.com/libjpeg-turbo/libjpeg-turbo/blob/2.1.5/LICENSE.md).
Copyright © 1991–2020 Thomas G. Lane и Guido Vollbeding.
Copyright © 2009–2023 D. R. Commander. Все права защищены.
Copyright © 2015 Viktor Szathmáry. Все права защищены.
- [zlib](https://github.com/madler/zlib) — потоковая компрессия и декомпрессия zlib. Динамически компонуется как системная библиотека.
Лицензия: [zlib License](https://github.com/madler/zlib/blob/develop/LICENSE)
Copyright (C) 1995–2026 Jean-loup Gailly и Mark Adler.
- [libdeflate](https://github.com/ebiggers/libdeflate) — быстрая компрессия всего буфера в формате zlib. Динамически компонуется как системная библиотека.
Лицензия: [MIT](https://github.com/ebiggers/libdeflate/blob/master/COPYING)
Copyright 2016 Eric Biggers.
Copyright 2024 Google LLC.
### Включённый код и ресурсы
- [Compact ICC Profiles](https://github.com/saucecontrol/Compact-ICC-Profiles) — встроенный ICC-профиль,
совместимый с Adobe.
Лицензия: [CC0 1.0 Universal](https://github.com/saucecontrol/Compact-ICC-Profiles/blob/master/license)
### Необязательный помощник для публикации в Bluesky
- Bryan Newbold / ATProto Hacker Cookbook — create_bsky_post.py — основа для [ответвлённого](https://gist.github.com/CleasbyCode/1eb678ca1fa1975b1c1e20aeec33637e) помощника для публикации в Bluesky (src/bsky/create_bsky_post.py).
Для справки см. [копию из Cookbook](https://github.com/bluesky-social/cookbook/blob/main/python-bsky-post/create_bsky_post.py)
Лицензия: [CC0 1.0 Universal](https://github.com/bluesky-social/cookbook/blob/main/LICENSE-CC0).
- Requests — HTTP-запросы и запросы к API Bluesky.
Лицензия: [Apache 2.0](https://github.com/psf/requests/blob/main/LICENSE) / [NOTICE](https://github.com/psf/requests/blob/main/NOTICE)
Copyright 2019 Kenneth Reitz.
- Beautiful Soup 4 — синтаксический анализ HTML и метаданных Open Graph.
Лицензия: [MIT](https://pypi.org/project/beautifulsoup4/)
Copyright (c) Leonard Richardson.
- Pillow — проверка изображений, размеры и обработка соотношения сторон.
Лицензия: [MIT-CMU](https://github.com/python-pillow/Pillow/blob/main/LICENSE)
PIL copyright © 1997–2011 Secret Labs AB и © 1995–2011 Fredrik Lundh и участники.
Pillow copyright © 2010 Jeffrey “Alex” Clark и участники.
##