Стеганография в BMP. Полное руководство

Erravielle

СТЕГАНОГРАФИЯ в BMP ИЗОБРАЖЕНИЯХ
Данная статья содержит готовый проект с подробным описанием алгоритма реализации шифрования текстового сообщения в графический файл формата *.BMPI. Теоретические предпосылки, или что нужно знать для начала работы . . .Пиксел изображения в 24-х битном .BMP формате занимает 3 Байта (24 бита) памяти (соответственно по 1 байту на каждый канал - Red, Green, Blue (RGB))Один символ текста занимает 1 Байт (8 бит) дискового пространства.Если заменять Байт изображения Байтом текста, то мы получим обсалютно другой цвет пиксела, и, как следствие, сильное искажение изображения. Поэтому наиболее удобно заменять только 1 бит одного из каналов Пиксела на 1 бит Текста. Такая подмена будет незаметна для человеческого глаза. (При желании можно заменять последний бит КАЖДОГО из каналов... Алгоритм изменится незначительно).Таким образом, один символ текста будет кодироваться в 8 пикселов изображения.Разумно задать следующий вопрос: "а какой бит заменять то?"Рассмотрим пример:Возьмем пиксел со значениями кодируемого канала: Red = 255; BYTE R = 255; // 11111111Предположим нам нужно заменить один бит этого канала на бит символа, равный '0'. Если мы заменим первый бит Байта R, то получим:BYTE R = 11111110; // теперь R = 127 (отсчет идет справа налево, а не слева направо!!! Вожно это знать!) - согласитесь, разница значений очень велика!Теперь попробуем заменить последний бит Пиксела:BYTE R = 01111111; // теперь R = 254 т.о. оттенок цвета изменится незначительно.Итак, решено - МЕНЯЕМ ПОСЛЕДНИЙ БИТ ПИКСЕЛА.II. АЛГОРИТМ РАБОТЫ ПРОГРАММЫ . . .ШИФРОВАНИЕ 1. Загружаем изображение 2. Определяем колличество пикселов, из которого оно состоит 3. Делим полученное значение на 8 и получаем максимальный размер текстового сообщения 3. Вводим шифуемый текст 4. Определяем размер текста и сопоставляем его с максимально допустимым 6. Определяем шаг продвижения по Битмапу 5. Организуем два цикла: - Внешний: обход по символам - Внутренний: обход по битам символа 6. В циклах заменяем биты изображения битами текста, двигаясь по изображению с заданным шагом 7. Сохраняем полученный результат 8. Записываем в файл "ключ", который позволит расшифровать сообщение! Ключ содержит две переменных: ШАГ и РАЗМЕР ТЕКСТАДЕШИФРОВКА 1. Загружаем изображение 2. Загружаем ключ 3. Организуем два цикла: - Внешний: обход по символам - Внутренний: обход по битам символа 4. В циклах считываем с шагом, указанным в ключе, значения последних битов пикселов; Группируем биты в символы и формируем из символов Текстовую строкуIII. РЕАЛИЗАЦИЯ . . .1. Объявим глобальные переменные, которые позволят нам продолжить работу:
Graphics::TBitmap *Bmp; // переменная для хранения BMP изображения
 
bool ERR_OPEN_BMP = true;  // переменные для отслеживания ошибок
bool ERR_OPEN_KEY = true;
bool ERR_NO_TEXT = true;
bool ERR_TEXT_LENGTH = false;
 
unsigned int IMAGE_SIZE = 0; // дополнительные переменные
unsigned int TEXT_SIZE = 0;
unsigned int MAX_TEXT_SIZE = 0;
2. Напишем функции для работы с Текстом и изображением:
TPoint GetPosition(TPoint Point, int Shag, int Width)
/* Функция оперделяет следующую координату пиксела, который будет кодироваться
   TPoint Point - предыдущая координата
   Shag - шаг кодирования
   Width - ширина Битмапа
*/
{  TPoint REZ;
   REZ.y += Point.y;
 
int BUF = Width - Point.x - Shag;
 
if (BUF <= 0) {
                REZ.y++;  BUF = abs(BUF);
                while (BUF >= Width)
                {BUF -= Width; REZ.y++;}
              }
              else BUF = REZ.x + Width - BUF;
REZ.x = BUF;
 
return REZ;
}
BYTE BinaryToByte (int *mass)
/* Собирает из массива битов один целый байт и возвращает его.
   Входной параметр: массив из восьми элементов, соответсвтующих
   битам Байта
*/
{
            BYTE Mask   = 00000001;
            BYTE Result = 00000000;
            BYTE Mask2;
 
            int j = 0;
            for (int i = 7; i > -1; i--, j++)
            {
              if (mass[i] == 1)  {Mask2 = (Mask << (j));
                                  Result = Result|Mask2;
                                  }
            }
            return Result;
}
int GetBitValue(BYTE B, int N)
/* Получает значение N-ого бита в байте B
   Возвращает 1 или 0 (в зависимости от значения Бита)
*/
{ int k = 256;
 
      for (int i = 0; i < N; i++) k /= 2;
 
      if ((B & k) != 0) return 1;
      else return 0;
}
BYTE ReadBitToByte (int Bit, BYTE B)
/* Записывает в байт B на последнюю позицию бит Bit
*/
{
     BYTE A = 00000001;
     BYTE Result = B;
 
     if (Bit == GetBitValue(B,8)) return B;
        else if (Bit == 1) return Result = Result|A;
             else if (Bit == 0) return B - A;
 
             return NULL;
}
Следующие функции не используются в проекте, но могут представлять для вас интерес:
int BinaryToDec (BYTE val)
/* Переводит Байт из двоичной системы в десятичную
*/
{   int Result = 0;
 
    for (int t = 1, i = 0; t < 129; t *= 2, i++)
            {
                if ((val & t) != 0) Result += pow(2,i);
            }
 return Result;
}
int *ByteToBinary (BYTE val)
/* Разбивает исходный Байт на биты. Возвращает массив из восьми элементов
   Каждый Nй элемент соответствует Nму биту.
   Принимает значения 1 или 0
*/
{           int *mass = new int[8];
            int t, i;
            for (t = 128, i = 0; t > 0; t /= 2, i++)
            {
                if ((val & t) != 0) mass[i] = 1;
                else if ((val & t) == 0) mass[i] = 0;
            }
  return mass;
}
К сожалению из за катастрофической нехватки времени не могу подробно описать как работает каждая из функций ((( возможно сделаю это чуть позже. . .3. Подготавливаем изображение для работы и определяем дополнительные переменные:
Bmp = new Graphics::TBitmap;
    Bmp->LoadFromFile(FileName); // копируем изображение в Bmp
    Bmp->PixelFormat = pf24bit; // устанавливаем глубину цвета в 24 bit (стандарт без Альфа канала)
 
IMAGE_SIZE = WIDTH*HEIGHT;
MAX_TEXT_SIZE = IMAGE_SIZE/8;
4. Введем шифруемый текс: Для этого я использовал компонент TRichEdit. Создаем свойство OnChange и пишем туда приблизительно такой код:
TEXT_SIZE = RichEdit1->Text.Length();
 
if (TEXT_SIZE != 0) ERR_NO_TEXT = false;
  else ERR_NO_TEXT = true;
 
if (TEXT_SIZE > MAX_TEXT_SIZE)
    {
       ERR_TEXT_LENGTH = true;
       Label9->Font->Color = clRed;
       Label12->Font->Color = clRed;
    }
    else
       {
         ERR_TEXT_LENGTH = false;
         Label9->Font->Color = clWhite;
         Label12->Font->Color = clWhite;
       }
Теперь при вводе символов в RichEdit1 программа будет самостоятельно высчитывать интересующие ее значения.5. Шифруем сообщение :
int Shag = MAX_TEXT_SIZE/TEXT_SIZE;
char *String = new char[RichEdit1->Text.Length()+1];// создаем переменную для хранения кодируемого текста
strcpy(String,RichEdit1->Text.c_str()); // копируем в нее кодируемый текст
 
 TPoint Next;    // позиция следующего кодируемого пиксела
 TPoint First;   // позиция кодированного пиксела
 BYTE TextByte;  // переменная для хранения символа
 
//---------------- ШИФРУЕМ СООБЩЕНИЕ -----------------//
 for (int i = 0; i < TEXT_SIZE; i++)  // перебор символов
    {
         TextByte = String[i]; // загоняем очередной символ
 
         for (int j = 0; j < 8; j++) // перебор битов символа
            {
             Next = GetPosition(First, Shag, WIDTH);
 
             TColor COLORR = Bmp->Canvas->Pixels[First.x][First.y];
 
             BYTE R=GetRValue(COLORR);    //
             BYTE G=GetGValue(COLORR);    // получили каналы
             BYTE B=GetBValue(COLORR);    //
 
             int bit = GetBitValue(TextByte, j+1); // получили записываемый бит
             R = ReadBitToByte (bit, R);         // записали jй бит в канал
 
             Bmp->Canvas->Pixels[First.x][First.y] = RGB(R,G,B);  // переопределили цвет
 
             First = Next;
            }
    }
//------------------ ЗАШИФРОВАЛИ ---------------------//
6. Сохраняем полученное изображение и ключ в файлы:
Bmp->SaveToFile(SavePictureDialog1->FileName); // изображение
 
  ofstream READ;
  READ.open(FileName);
// ключ
  READ<<StrToInt(Shag)<<endl;
  READ<<StrToInt(TEXT_SIZE);
ШИФРОВАНИЕ ГОТОВО!!!!Теперь ДЕШИФРОВКА:... как открыть файл мы теперь знаем ...1. Считываем коюч:
  ifstream OPEN;
  OPEN.open(Stego->OpenDialog1->FileName.c_str());
 
  OPEN>>SHAG>>KOL;
2. Собственно дешифруем и выводим результат:
TPoint Next;    // позиция следующего кодируемого пиксела
 TPoint First;   // позиция кодированного пиксела
 BYTE TextByte;  // переменная для хранения символа
 
 char *result = new char[KOL+1];
 strset(result, '\0');
 int *BitMass = new int[8]; // массив для хранения битов
 
    for (int i = 0; i < KOL; i++)  // перебор символов дешифруемого сообщения
        {
         for (int j = 0; j < 8; j++) // перебор битов символа
            {
             Next = GetPosition(First, SHAG, WIDTH);
 
             long COLORR = GetPixel(Bmp->Canvas->Handle,First.x,First.y);  // получили цвет
             BYTE R=GetRValue(COLORR);    //
             //BYTE G=GetGValue(COLORR);    // получили каналы
             //BYTE B=GetBValue(COLORR);    //
 
             BitMass[j] = GetBitValue(R, 8);
 
             First = Next;
            }
          result[i] =  BinaryToByte (BitMass);
          for (int k = 0; k < 8; k++) BitMass[k] = 0;
        }
 
 RichEdit1->Clear();
 result[KOL] = '\0';
 RichEdit1->Text = result;
Вот и Все, друзья... Во вложении вы найдети исходиники (среда разработки - C++ Builder 2009), краткую инструкцию по применению и примерчик.... Удачи в ваших начинаньях!!!
14 ответов

Erravielle

ну в нутри есть программа она использует ключ для шифрации а можно как нить без него просто изображение сохраять!!????????
Ах вот но что Тебе что конкретно надо? Самодельную программу, которая записывает файл bmp или тебе нужно просто записать bmp (например, посредством библиотечных функций)?


Erravielle

мне нужно просто шифровать текст в изображение BMP но без ключа ......текст нужно зашифровать в BMP... как я примерно понял нужно разбить картинку на куски (по 8 пикселей) и в одном таком куске будет храниться 1 буква через двоичный код (тиипа 00010101)...а при зашифровке и расшифровке чтобы не выскакивал сохранить и заагрузить ключ а что бы он просто сразу загружал изображение и расшифровывал то что есть в изображении......т.е.текст!!!!!!!


Erravielle

Я так понимаю, что тебе нужно исходники вот этой программы? Ну дык попроси автора да и всё. В этой теме тоже исходник лежит - бери его, да убирай ключ. Автор тут, можешь его спросить если что. Или ты не хочешь сам ничего делать, а тебе нужно всё готовенькое?


Erravielle

мне очень нужна вот эта программа Program.rar !!!!только без ключа!!! я уже чача три колыпаюсь и не могу убрать ключ!!!!не могли бы вы помоч??зарание большое спасибо!!!


Erravielle

Чувак, я б с радостью помог, но времени нет - сессия у меня... если не срочно то в июле помогу.... а щас никак....


Erravielle

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


Erravielle

Вопрос к автору проги (ну или кто разбираеться другой) какие конкртено функции отвечают за замену битов картинки ? Где что нужно поменять чтоб например записывалось не в последний бит, а в первый (ну вот надо мне так ) Я сам покопался, но пока не нашёл...
BYTE ReadBitToByte (int Bit, BYTE B) отвечает за это... видишь переменную A ??? это маска... вот с ней надо поколдовать чтобы заменить другой бит, ну еще и бит фонуцикй GetBitValue надо получать тот который интересует... опять же говорю - времени пока вообще нет, так что экспериментируйте )))


Erravielle

а как быть с GIFом???в инете об этом маловато....(


Erravielle

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


Erravielle

Вот выложил исходники. . . ! И еще одно замечание - BMP-файл можно представить в виде вектора, размерностью [width*height*3] что упростит продвижение по изображению и сделает ненужной функцию GetPixel. Но учтите, перевод BMP файла в вектор потребует определенные затраты времени. . .


Erravielle

Вот тут у нас поднялся вопрос о том, как засунуть информацию в *.jpg. Ибо передача файла *.bmp уже наводит на мысль о том, что там что-то спрятано. Поэтому кодирование информации в изображения с форматами бес сжатия - не есть интересно. Ты владеешь вопросом в части *.jpg?


Erravielle

вот такой вопрос а можно в этой проге ключ убрать?????


Erravielle

Про какой ключ идёт речь?


Erravielle

ну в нутри есть программа она использует ключ для шифрации а можно как нить без него просто изображение сохраять!!????????