В рамках проекта DarkJPEG разработан стеганографический веб-сервис нового поколения, позволяющий скрывать конфиденциальную информацию в виде незаметного шума в JPEG-изображениях, при этом выделить данную информацию можно только зная заданный при кодировании секретный ключ-пароль.
Проект разработан с целью реализации свободы информации людьми в тех странах, которые нарушают права человека, вводя цензуру средств информации или законодательно запрещая использование криптографии.
Сервис использует стойкие методы стеганографии для сокрытия самого факта сокрытия информации вместе со стойкими методами криптографии для защиты данных, передаваемых по открытым каналам, от компрометации (факта доступа посторонних лиц). Исходные тексты проекта распространяются в рамках лицензии MIT.
Основные особенности:
- Использование SHA-3 для генерации ключей;
- Симметричное шифрование AES-256;
- JPEG (DCT LSB) стеганография;
- Поддержка RarJPEG и двойного сокрытия;
- Подбор случайного контейнера;
- Вычисления без участия сервера;
- Гарантия полной конфиденциальности.
Intro
Стеганография — это наука о скрытой передаче информации путём сохранения в тайне самого факта передачи. Как правило, сообщение будет выглядеть как что-либо иное, например, как изображение, статья или письмо. Стеганографию обычно используют совместно с методами криптографии, таким образом, дополняя её. Преимущество стеганографии над чистой криптографией состоит в том, что сообщения не привлекают к себе внимания. Сообщения, факт шифрования которых не скрыт, вызывают подозрение и могут быть сами по себе уличающими в тех странах, в которых запрещена криптография. Таким образом, криптография защищает содержание сообщения, а стеганография защищает сам факт наличия каких-либо скрытых посланий.
Как всё началось
Идея создать доступный, быстрый, приватный, а главное полностью свободный стеганографический веб-сервис меня посетила чуть менее месяца назад. В связи с отдельными весёленькими законами, наспех принимаемыми в этой (да и не только) стране, меня посетила мысль, что как-то это ужасно неправильно, что у каждого человека должна быть возможность свободно обмениваться информацией (и не важно какой) по открытым и/или незащищённым каналам связи с другими людьми, и не заморачиваясь при этом изучением особенностей gpg-шифрования или сдувая пыль с таких утилит, как steghide, чтобы всё было кроссплатформенно, с действительно хорошим юзабилити и «здесь и сейчас». Поэтому я и загорелся идеей сделать подобный сервис, просто так, just for fun. И, несмотря на то, что это мой первый опыт в создании подобных сервисов, да и проектировщик интерфейсов из меня не ахти, за три недели увлекательной разработки по вечерам после работы, мне кажется, у меня всё получилось. Но давайте обо всём по порядку.
Метод
Для начала было изучено около десятка научных статей, раскрывающих то, какие вообще бывают методы стеганографии, их особенности реализации, анализ и детектирование. В качестве контейнеров было решено использовать изображения в формате JPEG, как самый распространённый тип контента в Интернете. На проверку, как оказалось, одни методы довольно легко выдают себя, используя нестандартные матрицы квантования, другие не проходят гистограммный тест, третье дают полезный объём порядка 9-13% размера контейнера, то есть если бы мы хотели спрятать 500Кб полезной информации, контейнер бы пришлось искать размером не менее 5Мб, а это довольно грустно.
В итоге, исследовав принцип работы довольно новых стеганографических методов F5 и на основе ошибки квантования, было решено использовать простой и банальный LSB (least significant bits), дополнив его предварительным шифрованием данных AES-256, что помимо возможности использования 256-битного ключа для кодирования, даёт на выходе псевдослучайную последовательность бит, чего как раз и добиваются в F5, путём случайных перестановок блоков данных. Таким образом, вот схематичное представление работы метода:
- берём 256-битную SHA-3 хэш-сумму введённого пользователем пароля + случайно сгенерированной соли;
- шифруем данные + заголовок (сигнатура, имя и размер вкладываемого файла) с помощью AES-256, добавляем в начало соль, в конец 0xFFD9 — маркер JPEG End of Image (о том зачем это сделано будет чуть ниже);
- подбираем контейнер, размером подходящий под наши данные, переводим цвета из RGB в YCbCr;
- над каждым блоком 8х8 пикселей производим дискретное косинусное преобразование;
- в последние два бита каждого ненулевого коэффициента понемногу записываем наши данные;
- коэффициенты сжимаются с использованием кодирования серий и кодов Хаффмана.
Как видно, в процессе JPEG-кодирования был пропущен шаг квантования коэффициентов с использованием соответствующих матриц, вместо которых в файл записываются одни единички — тем самым имитируется сжатие изображения с качеством 100%, что с одной стороны, конечно, заметно раздувает размер файла (прибавляя полезный объём для данных), с другой уменьшает подозрения, так как подобные единичные матрицы квантования совсем не редкость. Ура! Всё получилось! Полученный файл является полностью валидным JPEG с порядка 20% объёма, занимаемого нашими данными, его и смело отдаём пользователю. Декодирование осуществляется по подобной, обратной схеме.
Склейка и RarJPEG
На деле оказалось, что подобный метод даже довольно избыточен, на практике обычно хватает (если только не прячем что-то действительно серьёзное!) обычного добавления в конец JPEG файла наших зашифрованных данных. Вот здесь-то и пригождается фальшивый маркер конца картинки, который мы добавляем вручную — он хоть немного, но убирает подозрения от нашего приклеенного хвоста. Конкатенация файлов даёт ещё такую забавную возможность, как создание и обработка RarJPEG`ов, необходимо лишь приклеивать ZIP или RAR-файл и пропустить шаг с шифрованием, указывая пустой пароль, тогда до скрытого контента можно легко добраться, если полученную картинку открыть практически любым архиватором (но эта картинка по-прежнему валидный JPEG!)
Что в итоге
Таким образом, у нас есть три доступных варианта стеганографии: auto, join и steg. Авто по-умолчанию (мной так было решено) использует join для кодирования, склеивая файлы воедино (не обязательно с архивом — с чем угодно), единственное отличие, что только с join можно использовать пустой пароль для создания RarJPEG, а с auto и steg по соображениям безопасности нет. Существует ещё одна хитрая возможность: файл можно закодировать в контейнер steg методом, а потом к нему же прицепить что-нибудь join методом, это позволяет в случае «прижатия к стенке» выдать пароль от join-части, не скомпрометировав при этом steg-часть — получается такой контейнер с «двойным дном». К слову, если картинка будет хоть как-то изменена (обрезана, пережата etc.), никакая стеганография по определению, увы, не выживет, JPEG — формат сжатия с потерями качества.
Контейнеры
Что касается контейнеров, то здесь тоже три опции: rand, grad и image. Но тут всё гораздо проще: использующийся по умолчанию rand выкачивает случайную картинку с викимедии, grad используется в силу невозможности работы первого метода (например, когда нет интернета или объём данных слишком велик), image позволяет пользователю выбрать своё собственное изображение для контейнера. Существует ещё опция unsafe, включать которую рекомендуется исключительно владельцам слабеньких компьютеров, если у них по причине нехватки памяти не будет ничего работать, но до тех пор её использовать не рекомендуется всё по тем же соображениям безопасности.
Конфиденциальность
Перейдём к самой интересной и самой спорной части: конфиденциальность и анонимность. Вообще говоря, я согласен, что выражения «веб-сервис» и «конфиденциальность» в одном предложении звучат, мягко говоря, довольно странно. На самом деле всё далеко не так плохо. Весь код сервиса исполняется исключительно на клиенте, все вычисления никуда не вылезают за открытую вкладку браузера, никакая информация о действиях пользователей не отслеживается, не кешируется, не сохраняется, не логируется, никуда никак не передаётся. Более того, для его работы-то и веб-сервер, по большому счёту, никакой и не нужен, достаточно сохранить проект (или склонировать репозиторий) на диск и открыть index.html просто так, без установки какого-либо веб-сервера. Единственное, хром (или хромиум и его производные) нужно запускать с опциями -allow-file-access-from-files -disable-web-security для доступа к локальной ФС при кодировании и кросс-доменным загрузкам по ссылке при декодировании.
Разумеется, если будем рассматривать крайние случаи, как, например, взлом гитхаба, то существует опасность подмены скриптов, но не стоит, однако, при этом забывать, что подобному сценарию может быть подвержено абсолютно всё, что хоть как-то связано с внешним миром: так, возможно украсть приватный ключ мейнтейнера какого-либо ПО и использовать его для подписи изменённых пакетов, что, кстати, уже как-то наблюдалось. Единственное же отличие веб-сервиса в данном случае то, что он работает в гораздо более ограниченной песочнице, и максимум, что может при этом произойти: трекинг пользователей и компрометация данных. Поэтому я всем советую идеальное решение любых паранойя-проблем: пожалуйста, пользуйтесь моим сервисом через TOR (равно как, понятное дело, и при передаче кодированного контента по любым каналам связи).
Безопасность
Я не знаю как быстро, в случае острой необходимости, какие-нибудь люди с хмурыми лицами смогут определить через провайдер IP пользователей, но здесь, как и в случае с тем же I2P, можно лишь доказать сам факт посещения сервиса, действия пользователя отследить со стороны практически невозможно (если только не шпионить за самим человеком).
Что касается детектирования darkjpeg`ов на разных сайтах, так это будет немного затруднительно в случае использования join метода и, вообще говоря, довольно сложно при использовании steg. Например, для детектирования последнего подойдёт только ресурсоёмкий хи-квадрат тест, так что на счёт этого можно особо не переживать.
Если кому-то захочется раскодировать закодированные данные, не зная шифра, то следует помнить, что крипто-алгоритм использует 256-битный ключ, и если в качестве пароля не использовать всякие qwerty, 123 и прочие легко генерируемые по словарям сочетания, то остаётся только идти с раскалённым паяльником к отправителю (которого, к слову, ещё нужно умудриться найти, что совсем не тривиально в случае использования того же TOR; пожалуйста, используйте TOR), так как брутфорс в пару триллионов лет кажется сомнительным занятием.
Поддержка App Engine
Не менее, возможно, спорная часть — это использование Google App Service для получения картинок при вводе ссылки вместо указания самого файла. Так как у нас нет сервера (github pages, который умеет отдавать только статичные странички, не в счёт), нам нужно каким-то образом уметь загружать картинки (для декодирования) с разных сайтов. Существует ограничение, запрещающее выполнять кросс-доменные загрузки, если иное явно не указано тем сервером, с которого мы пытаемся загрузить контент, и это ограничение никак с наскоку, увы, не преодолеть. Существует четыре обходных пути, если мы всё-таки очень сильно хотим использовать кросс-доменную загрузку:
- сохранение файла на диск вручную и уже выбор его с локальной файловой системы — неудобно;
- использование локальной копии проекта, открытого с локальной файловой системы (file:///home/...) — не стоит того;
- автоматическое использование сервисом, в случае неудачи прямой загрузки, двух прокси-сервисов на App Engine платформе — удобно, но ограничение пересылаемой информации в 2Гб в день;
- использование уже написанных расширениий для браузеров, доступных с главной страницы проекта, для декодирования изображений напрямую с любых сайтов, через контекстное меню просто кликая по ним — идеально.
Тем не менее, не смотря на паранойю от слова «Google», прокси-сервис ровно так же самописный, он ничего не сохраняет, не кеширует, а просто принимает ссылку, закодированную base64, и выдает содержимое с дополнительным заголовком CORS, используется только если кросс-доменные загрузки на сайте или отключены, или просто не поддерживаются; исходники «обнимашек» — сервисов hugs-01 и hugs-02 — так же есть на гитхабе.
Разработчикам
Ядро проекта, dark.js, может быть использован вами в любых сторонних разработках. Он оформлен в виде асинхронного web worker`а и принимает следующие запросы:
- {action: "encrypt", name: "file.ext", pass: "password", buffer: ArrayBuffer}
- {action: "encode", method: "join", width: 0, height: 0, buffer: ArrayBuffer}
- {action: "encode", method: "auto | join | steg", width: image.width, height: image.height, buffer: ImageData}
- {action: "decode", method: "auto | join |steg", buffer: ArrayBuffer}
- {action: "decrypt", pass: "password"}
Ответы должны быть обработаны функцией worker.onmessage и выглядят так:
- {type: "encrypt", size: encrypted}
- {type: "encode", time: duration, isize: res.length, csize: enc.length, rate: 100*isize/csize, buffer: ArrayBuffer}
- {type: "decode", time: duration, isize: res.length, csize: dec.length, rate: 100*isize/csize}
- {type: "decrypt", name: "file.ext", buffer: ArrayBuffer}
- {type: "progress", name: "encrypt | decrypt | encode | decode", progress: percent}
- {type: "error", name: "encrypt | decrypt | encode | decode", msg: message}
При этом точный формат кодированного файла следующий:
- container: [ JPEG <+> encoded data ] or [ JPEG ][ encoded data ]
- encoded: [ 16-bit encryption salt ][ AES256 encrypted data ][ 0xFFD9 ]
- encrypted: [ 0x3141593 ][ 32-bit file size ][ 16-bit file name length ][ UTF-16 file name ][ DATA ][ zero padding ]
Почитать на досуге
- ru.wikipedia.org/wiki/Стеганография
- ru.wikipedia.org/wiki/Криптоанархизм
- en.wikipedia.org/wiki/Plausible_deniability
- www.activism.net/cypherpunk/manifesto.html
Summary
- работает только с современными браузерами (Safari 6, Chrome 25, Firefox 21, Opera 15 и все новее);
- да, Opera 12.xx, увы, не поддерживается по причине отсутствия поддержки в Presto многих html5-возможностей (таких как Blob URLs, download attribute, transferable objects etc.);
- рабочие скрипты минифицированны с помощью unglifyjs, для проверки отсутствия «закладок» можно запустить git clone, make и затем diff того, что получилось и того, что уже было в директории build до сборки — на клиент передаётся всё именно оттуда, напрямую с гитхаба;
- все вычисления действительно выполняются на клиенте, без малейшего участия сервера, можно сохранить проект себе на диск и просто открыть index.html без всяких апачей — всё будет работать;
- да, всё действительно конфидециально, ничто нигде не сохраняется, ничто не трекается, логи не ведутся — сервер github pages умеет отдавать только статичные странички;
- есть три метода кодирования: auto, join и steg, по-умолчанию используется auto, который в своей основе использует join;
- join склеивает файлы — размер вставляемых данных ограничен только здравым смыслом, безопасность средняя;
- steg использует самую настоящую DCT LSB стеганографию — допустимый размер вставляемых данных порядка 20% размера контейнера, безопасность высокая;
- отличие auto от join ещё в том, что в join можно кодировать пустым паролем, это даёт возможность создавать и обрабатывать RarJPEG;
- есть три типа контейнера: rand, grad и image, первый подбирает подходящую по размерам случайную картинку с викимедии, второй используется при невозможности первого, третий позволяет использовать любую указанную пользователем картинку, загружаемую с локальной ФС;
- файлы можно прикреплять с локальной ФС нажатием enter, кликом на плюсик, драг-и-дропом или по URL;
- все разноцветные стрелочки (и не только они) кликабельны;
- надпись «darkjpeg» так же кликабельна, действует как аналог обновления страницы без перезагрузки;
- в случае указывания URL для декодирования используются два прокси-сервиса на Google App Engine из-за ограничений CORS;
- для разработки можно использовать dark.js, содержащий JavaScript-реализации JPEG-энкодера и декодера, AES-256, SHA-3 и реализованный в виде асинхронного web worker`а;
- как будет использоваться мой сервис: для постинга ли пони на имиджбордах, или для координации каких-нибудь активистов — это на вашей совести, я всё разрешаю.
Лицензия
Данное программное обеспечение предоставляется «как есть», без каких-либо гарантий, явно выраженных или подразумеваемых, включая, но не ограничиваясь гарантиями товарной пригодности, соответствия по его конкретному назначению и отсутствия нарушений прав. Ни в каком случае авторы или правообладатели не несут ответственности по искам о возмещении ущерба, убытков или других требований по действующим контрактам, деликтам или иному, возникшим из, имеющим причиной или связанным с программным обеспечением или использованием программного обеспечения или иными действиями с программным обеспечением.
Благодарности
- Emily Stark, Mike Hamburg, Dan Boneh, Stanford University за их реализацию AES-256 на JavaScript;
- Chris Drost за его реализацию SHA-3 Keccak;
- Yury Delendik, Brendan Dahl, notmasteryet за части их JavaScript JPEG декодера;
- Andreas Ritter за его удивительный порт JPEG энкодера на JavaScript;
- Dan Gries за его примеры очень красивых фрактальных градиентов;
- Brsev за иконку шестерёнки из его набора Token Dark;
- Fabrizio Panattoni за его Premade Background 019;
- Моей девушке за вдохновение;
- Вам за прочтение, не судите строго :3
Автор: yndi