Стеганография: прячем данные в JPEG

в 10:13, , рубрики: Без рубрики

Как нам подсказывает Википедия, стеганография – это наука о скрытой передаче информации путём сохранения в тайне самого факта передачи. В отличие от криптографии, которая скрывает содержимое секретного сообщения, стеганография скрывает сам факт его существования. Стеганографию обычно используют совместно с методами криптографии, таким образом, дополняя её.

Преимущество стеганографии над чистой криптографией состоит в том, что сообщения не привлекают к себе внимания. Сообщения, факт шифрования которых не скрыт, вызывают подозрение и могут быть сами по себе уличающими в тех странах, в которых запрещена криптография. Таким образом, криптография защищает содержание сообщения, а стеганография защищает сам факт наличия каких-либо скрытых посланий.

Чтобы не загружать читателя, ограничусь в использовании формул и прочих строгих математических выкладок. В списке использованных источников приведены ссылки на книги, где подробно описана математическая модель стеганосистемы. Статья же делится на две части:
1. Теоретическая: схема типичной стеганосистемы;
2. Пример конкретной стеганосистемы (на основе изображений JPEG) и схема её реализации.

Описание стеганосистемы

Рассмотрим структурную схему типичной стеганосистемы. В общем случае стеганосистема может быть рассмотрена как система связи.
image

Основными стеганографическими понятиями являются сообщение и контейнер. Сообщение – это секретная информация, наличие которой необходимо скрыть. Контейнером называется несекретная информация, которую можно использовать для скрытия сообщения. В качестве сообщения и контейнера могут выступать как обычный текст, так и файлы мультимедийного формата.

Пустой контейнер (или так называемый контейнер-оригинал) – это контейнер, который не содержит скрытой информации. Заполненный контейнер (контейнер-результат) – контейнер, который содержит скрытую информацию. Одно из требований, которое при этом ставится: контейнер-результат не должен быть визуально отличим от контейнера-оригинала. Выделяют два основных типа контейнера: потоковый и фиксированный.

Потоковый контейнер представляет собой последовательность битов, которая непрерывно изменяется. Сообщение встраивается в него в реальном времени, поэтому в кодере заранее неизвестно, хватит ли размеров контейнера для передачи всего сообщения. В один контейнер большого размера может быть встроено несколько сообщений.

Основная проблема заключается в выполнении синхронизации, определении начала и конца последовательности. Если в данных контейнера существуют биты синхронизации, заголовки пакетов и т.д., то скрытая информация может следовать сразу же после них. Сложность организации синхронизации является преимуществом с точки зрения обеспечения скрытости передачи.

В фиксированном контейнере размеры и характеристики последнего заранее известны. Это позволяет выполнять вложение данных оптимальным (в определенном смысле) образом. Далее будем рассматривать фиксированные контейнеры.

Перед тем как выполнить вложение сообщения в контейнер, его необходимо преобразовать в определенный удобный для упаковки вид. Кроме того, перед упаковкой в контейнер, для повышения защищенности секретной информации последнюю можно зашифровать достаточно устойчивым криптографическим кодом. Во многих случаях также желательна устойчивость полученного стеганосообщения к искажениям (в том числе и злоумышленным).

В процессе передачи звук, изображение или какая-либо другая информация, используемая в качестве контейнера, может подвергаться разным трансформациям (в том числе с использованием алгоритмов с потерей данных): изменение объема, преобразование в другой формат и т.п., – поэтому для сохранения целостности встроенного сообщения может понадобиться использование кода с исправлением ошибок (помехоустойчивое кодирование). Начальную обработку скрываемой информации выполняет изображенный на рисунке прекодер.

Следует отметить, что для увеличения секретности встраивания, предварительная обработка довольно часто выполняется с использованием ключа.

Упаковка сообщения в контейнер (с учетом формата данных, представляющих контейнер) выполняется с помощью стеганокодера. Вложение происходит, например, путем модификации наименьших значащих битов контейнера. Вообще, именно алгоритм (стратегия) внесения элементов сообщения в контейнер определяет методы стеганографии, которые в свою очередь делятся на определенные группы, например, в зависимости от того, файл какого формата был выбран в качестве контейнера.

В большинстве стеганосистем для упаковки и извлечения сообщений используется ключ, который предопределяет секретный алгоритм, определяющий порядок внесения сообщения в контейнер. По аналогии с криптографией, тип ключа обуславливает существование двух типов стеганосистем:

  • с секретным ключом – используется один ключ, который определяется до начала обмена стеганограммой или передается защищенным каналом;
  • с открытым ключом – для упаковки и распаковки сообщения используются разные ключи, которые отличаются таким образом, что с помощью вычислений невозможно получить один ключ из другого, поэтому один из ключей (открытый) может свободно передаваться по незащищенному каналу.

В качестве секретного алгоритма может быть использован генератор псевдослучайной последовательности (ПСП) битов. Качественный генератор ПСП, ориентированный на использование в системах защиты информации, должен соответствовать определенным требованиям. Перечислим некоторые из них:

  • Криптографическая стойкость – отсутствие у нарушителя возможности предусмотреть следующий бит на основании известных ему предыдущих с вероятностью, отличной от 1/2. На практике криптографическая стойкость оценивается статистическими методами.
  • Хорошие статистические свойства – ПСП по своим статистическим свойствам не должна существенно отличаться от истинно случайной последовательности.
  • Большой период формированной последовательности.
  • Эффективная аппаратно-программная реализация.

Статистически (криптографически) безопасный генератор ПСП должен соответствовать следующим требованиям:

  • ни один статистический тест не определяет в ПСП никаких закономерностей, иными словами, не отличает эту последовательность от истинно случайной;
  • при инициализации случайными значениями генератор порождает статистически независимые псевдослучайные последовательности.

Следует отметить, что метод случайного выбора величины интервала между встроенными битами не является достаточно эффективным. Скрытые данные должны быть распределены по всему контейнеру, поэтому равномерное распределение длины интервалов (от наименьшего к наибольшему) может быть достигнуто только приблизительно, поскольку должна существовать уверенность в том, что все сообщение встроено (то есть, поместилось в контейнер).

Скрываемая информация заносится в соответствии с ключом в те биты, модификация которых не приводит к существенным искажениям контейнера. Эти биты образуют так называемый стеганопуть. Под «существенным» подразумевается искажение, которое приводит к росту вероятности выявления факта наличия скрытого сообщения после проведения стеганоанализа.

Стеганографический канал – канал передачи контейнера-результата (вообще, существование канала как, собственно говоря, и получателя – наиболее обобщенный случай, поскольку заполненный контейнер может, например, храниться у «отправителя», который поставил перед собой цель ограничить неавторизованный доступ к определенной информации. В данном случае отправитель выступает в роли получателя).

В стеганодетекторе определяется наличие в контейнере (возможно уже измененном) скрытых данных. Различают стеганодетекторы, предназначенные только для обнаружения факта наличия встроенного сообщения, и устройства, предназначенные для выделения этого сообщения из контейнера, – стеганодекодеры.

Итак, в стеганосистеме происходит объединение двух типов информации таким образом, чтобы они по-разному воспринимались принципиально разными детекторами. В качестве одного из детекторов выступает система выделения скрытого сообщения, в качестве другого – человек.

Алгоритм встраивания сообщения в простейшем случае состоит из двух основных этапов:

  1. Встраивание в стеганокодере секретного сообщения в контейнер-оригинал.
  2. Обнаружение (выделение) в стеганодетекторе (декодере) скрытого зашифрованного сообщения из контейнера-результата.

Про JPEG

В нашей работе будем реализовывать стеганосистему на основе JPEG. JPEG – это метод сжатия изображений с потерями. Он прекрасно сжимает изображения с непрерывными тонами, в которых близкие пикселы обычно имеют схожие цвета, но не очень хорошо справляется с двухуровневыми черно-белыми образами.

Для начала кратко рассмотрим алгоритм JPEG, более подробное описание и примеры можно найти в книгах, указанных в источниках. Кодировщик 8-битных RGB-изображений можно описать семью пунктами (на вход подаётся массив компонент изображения):

  1. Преобразование цветового пространства. Цветное изображение преобразуется из RGB в представление светимость/цветность. Глаз чувствителен к малым изменениям яркости пикселов, но не цветности, поэтому из компоненты цветности можно удалить значительную долю информации для достижения высокого сжатия без заметного визуального ухудшения качества образа. Этот шаг не является обязательным, но он очень важен, так как остальная часть алгоритма будет независимо работать с каждым цветным компонентом. Без преобразования пространства цветов из компонентов RGB нельзя удалить существенную часть информации, что не позволяет сделать сильное сжатие.
  2. Субдискретизация. Для более эффективного сжатия цветное изображение разбивается на крупные пикселы. Укрупнение пикселов либо вообще не делается (укрупнение 1hv1 или «4:4:4»), либо же делается или в соотношении 2:1 по горизонтали и вертикали (укрупнение 2h2v или «4:1:1») или в пропорциях 2:1 по горизонтали и 1:1 по вертикали (укрупнение 2h1v или «4:2:2»).
  3. Соединение в блоки. Пикселы каждой цветной компоненты собираются в блоки 8x8, которые называются единицами данных (Minimum Coded Unit). Если число строк или столбцов изображения не кратно 8, то самая нижняя строка и самый правый столбец повторяются нужное число раз.
  4. Дискретное косинус-преобразование. К каждой единице данных применяется дискретное косинус-преобразование (DCT), в результате чего получаются блоки 8x8 частот единиц данных. Они содержат среднее значение пикселов единиц данных и следующие поправки для высоких частот. Это позволяет представить данные в виде, позволяющем более эффективное сжатие.
  5. Квантование. Каждая из 64 компонент частот единиц данных делится на специальное число, называемое коэффициентами квантования (QC), которая округляется до целого. Здесь информация невосполнимо теряется. Но в нашем кодировщике этот шаг опускается для увеличения количества записываемой информации (т.е. все коэффициенты квантования равны единице, качество JPEG – 100%).
  6. Сжатие без потерь. Все 64 квантованных частотных коэффициента каждой единицы данных кодируются с помощью комбинации RLE и метода Хаффмана.
  7. Добавление заголовков и запись в файл. На последнем шаге добавляется заголовок из использованных параметров JPEG и результат выводится в сжатый файл.

Алгоритм симметричный, так что кодировщик будет выполнять обратные действия.

Стеганография в JPEG

Приступим к рассмотрению стеганосистемы на основе изображений формата JPEG. В основе лежит простейший метод LSB (Least Significant Bit, наименее значащий бит).

Суть метода LSB заключается в том, что человек в большинстве случаев не способен заметить изменений в последнем бите цветовых компонент изображения. Фактически, LSB – это шум, поэтому его можно использовать для встраивания информации путем замены менее значащих битов пикселей изображения битами секретного сообщения. Метод работает с растровыми изображениями, представленными в формате без компрессии (например, BMP). Основной недостаток метода – высокая чувствительность к малейшим искажениям контейнера.

В нашей же стеганосистеме будем применять DCT LSB стеганографию. Основное отличие заключается в том, что данные записываются не в цветовые компоненты, а в коэффициенты дискретного косинус-преобразования. Рассмотрим схему кодировщика (декодер делает всё тоже самое, но в обратном порядке).

На входе: цветное изображение, скрываемые данные, пароль.
На выходе: изображение в формате JPEG со скрытыми данными.

  1. Генерация ключей. Для работы кодировщика необходимы 2 ключа: стегано- и криптоключ. Возьмем хэш-сумму SHA-256 введённого пользователем пароля. Первые 16 байт будем использовать для стеганоключа, вторые – для криптоключа.
  2. Предварительная обработка текста (прекодер). Повторно берем хэш-сумму криптоключа и получаем новые 32 байта, которые уже будут использоваться для шифрования данных. Данные шифруем с помощью алгоритма AES-256.
  3. Начинаем кодировать изображение. Проводим первые 4 шага ранее рассмотренного алгоритма JPEG.
  4. Вместо пятого шага (квантование) алгоритма JPEG прячем наши данные.
    • Анализатор формата. Чтобы сделать вмешательство в изображение незаметным, будем проводить «визуальный» анализ. Каждый последний бит коэффициента блока инвертируется, и считается метрика PSNR для исходного и измененного блоков. Если значение метрики меньше 55 дБ, то в данный блок запись не производится. Т.к. при значениях метрики больше 40 дБ изображения считаются практически идентичными для человеческого глаза, то при 55 дБ разница точно будет незаметна для глаза.
    • Стеганопуть. Стеганоключ представляется в двоичном виде, и каждому блоку ставится в соответствие соответствующий бит двоичной последовательности (по модулю). Если бит равен единице, то блок используется для записи, если нулю – то отбрасывается.
    • Стеганокодер. Проводим стандартную процедуру LSB для каждого блока 8х8: записываем данные в каждый элемент, значение которого больше единицы.
  5. Продолжаем выполнение алгоритма JPEG (сжатие без потерь и запись в файл).

Выводы и заключение

По приведенной схеме стеганосистемы можно легко написать программу. Исходные коды моей реализации можно найти на GitHub. Программу нельзя назвать полноценной, скорее альфа-версией, но основной функционал в ней реализован. Четвертый пункт для кодировщика реализован здесь, а для декодировщика здесь. За основу взяты уже готовые реализации JPEG, SHA-256, AES-256.

Кодирование-декодирование

void jpeg_encoder::code_block(int component_num)
{
    DCT2D(m_sample_array);
    load_quantized_coefficients(component_num);

    double psnr = 0;
    for (int i = 0; i < 64; i++)
    {
        if (m_coefficient_array[i] > 1)
        {
            short t = m_sample_array[i];
            bit_stream::write_bit(t, bit_stream::read_bit(t) == 1 ? 0 : 1);
            psnr += (t - m_sample_array[i]) * (t - m_sample_array[i]);
        }
    }
    psnr /= 64;
    if (psnr != 0)
        psnr = 20 * log10(255 / sqrt(psnr));
    else
        psnr = 70;

    if (psnr > 55)
    {
        for (int i = 0; i < 8; i++)
        {
            for (int j = 0; j < 8; j++)
            {
                if (m_coefficient_array[8 * i + j] > 1)
                {
                    int bits = bitstr->get_next_bit();
                    if (bits != -1)
                    {
                        bit_stream::write_bit(m_coefficient_array[8 * i + j], bits);
                    }
                    bitr++;
                }
            }
        }
    }

    if (m_pass_num == 1)
        code_coefficients_pass_one(component_num);
    else
        code_coefficients_pass_two(component_num);
}

void jpeg_decoder::transform_mcu(int mcu_row)
{
    jpgd_block_t* pSrc_ptr = m_pMCU_coefficients;
    uint8* pDst_ptr = m_pSample_buf + mcu_row * m_blocks_per_mcu * 64;

    for (int mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++)
    {
        idct(pSrc_ptr, pDst_ptr, m_mcu_block_max_zag[mcu_block]);

        double psnr = 0;
        for (int i = 0; i < 64; i++)
        {
            if (pSrc_ptr[i] > 1)
            {
                short t = pDst_ptr[i];
                bit_stream::write_bit(t, bit_stream::read_bit(t) == 1 ? 0 : 1);
                psnr += (t - pDst_ptr[i]) * (t - pDst_ptr[i]);
            }
        }
        psnr /= 64;
        if (psnr != 0)
            psnr = 20 * log10(255 / sqrt(psnr));
        else
            psnr = 70;

        if (!done && psnr > 55)
        {
            for (int i = 0; i < 8; i++)
            {
                for (int j = 0; j < 8; j++)
                {
                    if (pSrc_ptr[g_ZAG[8 * i + j]] > 1)
                    {
                        int bits = bit_stream::read_bit(pSrc_ptr[g_ZAG[8 * i + j]]);

                        if (bitstr->set_next_bit(bits) == -1)
                        {
                            done = true;

                            int size = bitstr->get_readed_size();
                            char* str = new char[size];
                            bitstr->get_data(str);
                            str[size] = '';

                            m_stparams->stego_data = str;
                        }
                    }
                }
            }
        }

        pSrc_ptr += 64;
        pDst_ptr += 64;
    }
}

В заключение приведем характеристики полученного продукта:

  • JPEG (DCT LSB) стеганография;
  • Двухуровневая защита информации;
  • Использование SHA-256 для генерации ключей;
  • Симметричное шифрование текста AES-256;
  • До 30% скрываемой информации от размера контейнера в зависимости от типа изображения;
  • Отсутствие помехозащищенности.

Источники и дополнительные ссылки

  1. Коханович Г.Ф., Пузыренко А.Ю. Компьютерная стеганография. Теория и практика. – К.: «МК-Пресс», 2006.
  2. Д.Сэломон. Сжатие данных, изображений и звука. Москва: Техно-сфера, 2004.
  3. Ватолин Д., Ратушняк А., Смирнов М., Юкин В. Методы сжатия данных. Устройство архиваторов, сжатие изображений и видео. — М.: ДИАЛОГ-МИФИ, 2003.
  4. en.wikipedia.org/wiki/JPEG

Автор: m0j0

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js