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

Стеганографией называется способ сокрытия информации когда скрывается факт наличия некой секретной информации на её носителе. При этом сама информация остается неизменной в этом отличие от стеганографии от криптографии. Слово стеганография образовано парой греческих слов steganos — скрытие, grapho — пишу и является одним из нескольких слов в русском языке, образованных от греческого слова grapho, например, каллиграфия (чистописание), стенография (быстрая запись), фотография, типография, литография, порнография :)

Первым случаем применения канонической стеганографии, считается исторический анекдот, описанный Геродотом. В V в до н.э. греческий правитель Гистий попал в плен к персидскому царю Дарию. Находясь под надзором царя Дария в Сузах, он должен был послать секретное сообщение своему родственнику в анатолийский город Милет. Не имея возможности послать своим родным обычное письмо, которое Дарий обязательно задержал бы, Гистий выбрил голову своему рабу и вытутаировал на ней короткое сообщение, а затем подождал пока волосы снова отрастут. Согласно этой легенде курьеру удалось доставить тайное письмо родственникам Гистия.

Криптографический фронт великой отечественной.

Компьютерная стеганография

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

Алгоритм записи

Для сокрытия информации один байт передаваемой информации разделяется на три группы бит, каждая из которых записывается в определённый цветовой канал точки изображения. Диапазон значений одного канала составляет 0-255. Если байт исходного сообщения разделяется на три группы по 3-3-2 бита на канал, то среднее искажение цвета при записи составит примерно 5% на канал, разница между исходныи и модифицированным изображением будет практически незаметной.

Реализуем данный алгоритм и посмотрим, насколько могут быть заметны вносимые искажения. В качестве скрываемеого сообщения возьмем фрагмент трагедии В. Шекспира «Кориолан».

Войска собрались, но никто не знает,
Пошлют их на восток или на запад.
Хлеб дорог, чернь бунтует, и, по слухам,
Коминий, Марций, враг твой давний (в Риме
Его сильней, чем сам ты, ненавидят),
И римлянин отважнейший Тит Ларций
Поход подготовляют на кого-то,
Всего же вероятней - на тебя.
Будь начеку

В. Шекспир «Кориолан»

В качестве основы используем три разных изображения, белое, полностью чёрное, и "обычное".

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

Замечания по реализации

SteganographyEncoder.cs класс, реализующий алгоритм.

Создание класса

Для уменьшения количества операций выделения памяти используется временный буфер _cache, который создается в момент создания объекта класса. Количество бит информации, кодируемое в каждом канале определяется переменной _channelLayout. При определении разбиения по каналам вычисляются маски для кодирования и декодирования информации _channelMask и _channelMaskInv.

public void SetChannelMask(byte r, byte g, byte b)
{
  if (r + g + b > 8)
  {
    Debug.Assert(false);
    throw new ArgumentOutOfRangeException("Некорректные значения кодировки цветовых каналов.");
  }

  _channelLayout[0] = r;
  _channelLayout[1] = g;
  _channelLayout[2] = b;

  for (int i = 0; i < 3; i++)
  {
    //Битовая маска для "вырезания" нужного количества бит из байта данных/цветового канала
    _channelMaskInv[i] = (byte)(0xFF << _channelLayout[i]);  // 11111000 (пример для 3)
    _channelMask[i]    = (byte)(~_channelMaskInv[i]);        // 00000111 (пример для 3)
  }
}

Кодирование информации

Для записи байта информации получается цвет точки, в которую будет произведена запись. Далее, в временную переменную _cache записываются значения R,G,B компоненты цвета точки. Для записи данных нам нужно все младшие биты (в которые будет произведена запись) цветового канала выставить в 1. Все старшие биты сообщения (те, которые не записываются на данном шаге) необходимо установить в 1, что бы не вносить искажения в старшие биты цветового канала. Новое значение цветового канала получается объединением двух байт с использованием логического И.

кодирование данных
private void EncodeByte(byte data, int x, int y)
{
  Color pixel = _encodedImage.GetPixel(x, y);

  _cache[0] = pixel.R;
  _cache[1] = pixel.G;
  _cache[2] = pixel.B;

  for (int i = 0; i < 3; i++)
  {
    //для того, что бы не вносить искажения в цвет, хранящийся в старших биты цветового канала
    //принудительно обнуляем крайние левые биты данных для записи в текущий канал
    //а затем выставляем крайние левые биты данных канала в 1
    byte channelData = (byte)(data & _channelMask[i]);
    channelData |= _channelMaskInv[i];

    //что бы не вносить искажения в исходные данные принудительно выставляем 
    //крайние правые биты цветового канала в 1, 
    _cache[i] |= _channelMask[i];

    //кодируем данные в цветовой канал
    _cache[i] &= channelData;

    data >>= _channelLayout[i];
  }

  Color encoded = Color.FromArgb(pixel.A, _cache[0], _cache[1], _cache[2]);

  _encodedImage.SetPixel(x, y, encoded);
}

Декодирование данных

Декодирование данных тривиально, вычитываем биты с цветовых каналов согласно группировке и объединяем их в байт используя логическое ИЛИ.

private byte DecodeByte(int x, int y)
{
  Color pixel = _encodedImage.GetPixel(x, y);

  int data = (pixel.B & _channelMask[2]);
  data <<= _channelLayout[1];
  data |= (pixel.G & _channelMask[1]);
  data <<= _channelLayout[0];
  data |= (pixel.R & _channelMask[0]);

  return (byte)data;
}

Практическое применение

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

Пример использования

Тестовый метод, использованный мной для тестирования алгоритма и создания иллюстраций для статьи.

[TestMethod]
public void TestEncoding()
{
  byte[] sourceData = Encoding.Unicode.GetBytes(message);

  KeyValuePair<string, Bitmap>[] samples = new KeyValuePair<string, Bitmap>[] 
  { 
    new KeyValuePair<string, Bitmap>("white_enc.png", Resources.white), 
    new KeyValuePair<string, Bitmap>("black_enc.png", Resources.black), 
    new KeyValuePair<string, Bitmap>("image_enc.png", Resources.image) 
  };

  foreach (KeyValuePair<string, Bitmap> im in samples)
  {
    Bitmap result = (Bitmap)im.Value.Clone();

    SteganographyEncoder imageEncoder = new SteganographyEncoder(result, EncodeArea);
    imageEncoder.EncodeData(sourceData);
    result.Save(Path.Combine(@"D:\Temp", im.Key));

    SteganographyEncoder imageDecoder = new SteganographyEncoder(result, EncodeArea);

    byte[] destData = imageDecoder.DecodeData();
    string decodedMessage = Encoding.Unicode.GetString(destData);

    Assert.AreEqual(message, decodedMessage);
  }
}