Доброго времени суток!
На Хабрахабре уже есть статьи на данную тему и я хочу поделится своим вариантом решения данной задачи. И так, начнем.
Стеганографическая система (стегосистема) — объединение методов и средств используемых для создания скрытого канала для передачи информации. Стеганография в наше время часто применяется, как правило, для встраивания цифровых водяных знаков, являющееся основой для систем защиты авторских прав и DRM (Digital rights management) систем.
Реализовывать будем метод LSB.
LSB (Least Significant Bit, наименьший значащий бит) — суть этого метода заключается в замене последних значащих битов в контейнере (изображения, аудио или видеозаписи) на биты скрываемого сообщения. Разница между пустым и заполненным контейнерами должна быть не ощутима для органов восприятия человека.
Также будем использовать для шифрования/дешифрования bmp файл, не содержащий палитру. В таком bmp файле каждые 3 байта определяют 3 цвета пикселя.
Так как, мы будем работать с битами информации, а цвет одного пикселя занимает один байт, то потребуются методы преобразования байта в биты и наоборот, которые представлены ниже:
Я думаю они понятны и пояснять не стоит.
И так, расположение в bmp информации будет следующим:
Для начала рассмотрим код, записывающий в пиксель 0.0 признак зашифрованного файла.
В коде в переменной Symbol хранится код символа "/". Далее этот код преобразуется в массив бит (переменная ArrBeginSymbol). Цвет пикселя 0.0 хранится в переменной curColor. Далее каждый из трех составляющих цветов пикселя преобразуется в массив бит, затем в красном цвете заменяются младшие 2 бита на биты символа "/", в зеленом заменяются младшие 3 бита на биты символа "/" и в синем так же заменяются младшие 3 бита цвета. Из 3 новых полученных цветов создается новый цвет пикселя (nColor) и устанавливается вместо предыдущего цвета. Все, признак того, что в файле есть информация записан в bmp файл.
Способ записи информации, то есть 2 бита, 3 бита и 3 бита выбран для удобства работы, ибо в один пиксель записывается сразу байт информации.
Далее рассмотрим метод проверки признака, описанного выше
Метод аналогичен коду приведенному выше с точностью до наоборот. Если в пикселе 0.0 записан символ "/", то функция возвращает true, иначе — false.
Далее в файл записывается размер текстовой информации. Рассмотрим метод подробнее:
В CountSymbols записывается количество символов исходного текста. Каждая цифра занимает один байт, поэтому максимальная длина исходного текста — 999 — 4 = 995 символов(4 — это один пиксель на признак присутствия информации в файле и три пикселя на размер текстовой информации). При необходимости можно увеличить, взяв пиксели не с 0.1 по 0.3, а с 0.1 по 0.4 например, и так далее. В цикле for каждая цифра количества исходног�� текста преобразуется в массив бит и записывается в младшие пиксели цвета по принципу, описанному выше.
Метод чтения размера текстовой информации:
Метод обратный функции WriteCountText. Пояснять, я думаю, не стоит.
Опустим код, который открывает/закрывает файл, проверяет на ошибки, его можно будет посмотреть, скачав проект. Приведем код, который собственно и записывает информацию в файл. Некоторый код уже был приведен выше.
Bitmap bPic — открытый файл с картинкой.
И, соответственно код, который считывает информацию из bmp файла
Метод LSB не сложен в реализации, и его можно использовать для сокрытия нужной информации в bmp файле. Но существенным недостатком метода является то, что размер bmp имеет большой размер, что делает этот метод нежизнеспособным для передачи конфиденциальной информации по сети интернет.
Сам проект: github.com/Finnetrib/StegProject
Картинка без текста drive.google.com/open?id=1yhUrJYGasCl2UAPgkO6kinFAK55GS4jW
Картинка с текстом drive.google.com/open?id=1aHmy_gTIcJRLr1fz-ehBmxSmg3MQfQkw
На Хабрахабре уже есть статьи на данную тему и я хочу поделится своим вариантом решения данной задачи. И так, начнем.
Общие сведения
Стеганографическая система (стегосистема) — объединение методов и средств используемых для создания скрытого канала для передачи информации. Стеганография в наше время часто применяется, как правило, для встраивания цифровых водяных знаков, являющееся основой для систем защиты авторских прав и DRM (Digital rights management) систем.
Реализовывать будем метод LSB.
LSB (Least Significant Bit, наименьший значащий бит) — суть этого метода заключается в замене последних значащих битов в контейнере (изображения, аудио или видеозаписи) на биты скрываемого сообщения. Разница между пустым и заполненным контейнерами должна быть не ощутима для органов восприятия человека.
Также будем использовать для шифрования/дешифрования bmp файл, не содержащий палитру. В таком bmp файле каждые 3 байта определяют 3 цвета пикселя.
Подготовка к реализации
Так как, мы будем работать с битами информации, а цвет одного пикселя занимает один байт, то потребуются методы преобразования байта в биты и наоборот, которые представлены ниже:
private BitArray ByteToBit(byte src) { BitArray bitArray = new BitArray(8); bool st = false; for (int i = 0; i < 8; i++) { if ((src >> i & 1) == 1) { st = true; } else st = false; bitArray[i] = st; } return bitArray; } private byte BitToByte(BitArray scr) { byte num = 0; for (int i = 0; i < scr.Count; i++) if (scr[i] == true) num += (byte)Math.Pow(2, i); return num; }
Я думаю они понятны и пояснять не стоит.
И так, расположение в bmp информации будет следующим:
- Пиксель 0,0: признак того, что в файле есть текстовая информация. В качестве признака используется символ /
- Пиксели 0.1 — 0.3: размер текстовой информации, записанной в файл
- Пиксели 0.4 и до конца файла: собственно текстовая информация
Для начала рассмотрим код, записывающий в пиксель 0.0 признак зашифрованного файла.
byte [] Symbol = Encoding.GetEncoding(1251).GetBytes("/"); BitArray ArrBeginSymbol = ByteToBit(Symbol[0]); Color curColor = bPic.GetPixel(0, 0); BitArray tempArray = ByteToBit(curColor.R); tempArray[0] = ArrBeginSymbol[0]; tempArray[1] = ArrBeginSymbol[1]; byte nR = BitToByte(tempArray); tempArray = ByteToBit(curColor.G); tempArray[0] = ArrBeginSymbol[2]; tempArray[1] = ArrBeginSymbol[3]; tempArray[2] = ArrBeginSymbol[4]; byte nG = BitToByte(tempArray); tempArray = ByteToBit(curColor.B); tempArray[0] = ArrBeginSymbol[5]; tempArray[1] = ArrBeginSymbol[6]; tempArray[2] = ArrBeginSymbol[7]; byte nB = BitToByte(tempArray); Color nColor = Color.FromArgb(nR, nG, nB); bPic.SetPixel(0, 0, nColor);
В коде в переменной Symbol хранится код символа "/". Далее этот код преобразуется в массив бит (переменная ArrBeginSymbol). Цвет пикселя 0.0 хранится в переменной curColor. Далее каждый из трех составляющих цветов пикселя преобразуется в массив бит, затем в красном цвете заменяются младшие 2 бита на биты символа "/", в зеленом заменяются младшие 3 бита на биты символа "/" и в синем так же заменяются младшие 3 бита цвета. Из 3 новых полученных цветов создается новый цвет пикселя (nColor) и устанавливается вместо предыдущего цвета. Все, признак того, что в файле есть информация записан в bmp файл.
Способ записи информации, то есть 2 бита, 3 бита и 3 бита выбран для удобства работы, ибо в один пиксель записывается сразу байт информации.
Далее рассмотрим метод проверки признака, описанного выше
private bool isEncryption(Bitmap scr) { byte[] rez = new byte[1]; Color color = scr.GetPixel(0, 0); BitArray colorArray = ByteToBit(color.R); //получаем байт цвета и преобразуем в массив бит BitArray messageArray = ByteToBit(color.R); ;//инициализируем результирующий массив бит messageArray[0] = colorArray[0]; messageArray[1] = colorArray[1]; colorArray = ByteToBit(color.G);//получаем байт цвета и преобразуем в массив бит messageArray[2] = colorArray[0]; messageArray[3] = colorArray[1]; messageArray[4] = colorArray[2]; colorArray = ByteToBit(color.B);//получаем байт цвета и преобразуем в массив бит messageArray[5] = colorArray[0]; messageArray[6] = colorArray[1]; messageArray[7] = colorArray[2]; rez[0] = BitToByte(messageArray); //получаем байт символа, записанного в 1 пикселе string m = Encoding.GetEncoding(1251).GetString(rez); if (m == "/") { return true; } else return false; }
Метод аналогичен коду приведенному выше с точностью до наоборот. Если в пикселе 0.0 записан символ "/", то функция возвращает true, иначе — false.
Далее в файл записывается размер текстовой информации. Рассмотрим метод подробнее:
private void WriteCountText(int count, Bitmap src) { byte[] CountSymbols = Encoding.GetEncoding(1251).GetBytes(count.ToString()); for (int i = 0; i < 3; i++) { BitArray bitCount = ByteToBit(CountSymbols[i]); //биты количества символов Color pColor = src.GetPixel(0, i + 1); //1, 2, 3 пикселы BitArray bitsCurColor = ByteToBit(pColor.R); //бит цветов текущего пикселя bitsCurColor[0] = bitCount[0]; bitsCurColor[1] = bitCount[1]; byte nR = BitToByte(bitsCurColor); //новый бит цвета пиксея bitsCurColor = ByteToBit(pColor.G);//бит бит цветов текущего пикселя bitsCurColor[0] = bitCount[2]; bitsCurColor[1] = bitCount[3]; bitsCurColor[2] = bitCount[4]; byte nG = BitToByte(bitsCurColor);//новый цвет пиксея bitsCurColor = ByteToBit(pColor.B);//бит бит цветов текущего пикселя bitsCurColor[0] = bitCount[5]; bitsCurColor[1] = bitCount[6]; bitsCurColor[2] = bitCount[7]; byte nB = BitToByte(bitsCurColor);//новый цвет пиксея Color nColor = Color.FromArgb(nR, nG, nB); //новый цвет из полученных битов src.SetPixel(0, i + 1, nColor); //записали полученный цвет в картинку } }
В CountSymbols записывается количество символов исходного текста. Каждая цифра занимает один байт, поэтому максимальная длина исходного текста — 999 — 4 = 995 символов(4 — это один пиксель на признак присутствия информации в файле и три пикселя на размер текстовой информации). При необходимости можно увеличить, взяв пиксели не с 0.1 по 0.3, а с 0.1 по 0.4 например, и так далее. В цикле for каждая цифра количества исходног�� текста преобразуется в массив бит и записывается в младшие пиксели цвета по принципу, описанному выше.
Метод чтения размера текстовой информации:
private int ReadCountText(Bitmap src) { byte[] rez = new byte[3]; //массив на 3 элемента, т.е. максимум 999 символов шифруется for (int i = 0; i < 3; i++) { Color color = src.GetPixel(0, i + 1); //цвет 1, 2, 3 пикселей BitArray colorArray = ByteToBit(color.R); //биты цвета BitArray bitCount = ByteToBit(color.R); ; //инициализация результирующего массива бит bitCount[0] = colorArray[0]; bitCount[1] = colorArray[1]; colorArray = ByteToBit(color.G); bitCount[2] = colorArray[0]; bitCount[3] = colorArray[1]; bitCount[4] = colorArray[2]; colorArray = ByteToBit(color.B); bitCount[5] = colorArray[0]; bitCount[6] = colorArray[1]; bitCount[7] = colorArray[2]; rez[i] = BitToByte(bitCount); } string m = Encoding.GetEncoding(1251).GetString(rez); return Convert.ToInt32(m, 10); }
Метод обратный функции WriteCountText. Пояснять, я думаю, не стоит.
Реализация
Опустим код, который открывает/закрывает файл, проверяет на ошибки, его можно будет посмотреть, скачав проект. Приведем код, который собственно и записывает информацию в файл. Некоторый код уже был приведен выше.
Bitmap bPic — открытый файл с картинкой.
BinaryReader bText = new BinaryReader(rText, Encoding.ASCII); List<byte> bList = new List<byte>(); while (bText.PeekChar() != -1) { //считали весь текстовый файл для шифрования в лист байт bList.Add(bText.ReadByte()); } int CountText = bList.Count; // в CountText - количество в байтах текста, который нужно закодировать bText.Close(); rFile.Close(); //проверяем, поместиться ли исходный текст в картинке if (CountText > ((bPic.Width * bPic.Height)) - 4 ) { MessageBox.Show("Выбранная картинка мала для размещения выбранного текста", "Информация", MessageBoxButtons.OK); return; } //проверяем, может быть картинка уже зашифрована if (isEncryption(bPic)) { MessageBox.Show("Файл уже зашифрован", "Информация", MessageBoxButtons.OK); return; } byte [] Symbol = Encoding.GetEncoding(1251).GetBytes("/"); BitArray ArrBeginSymbol = ByteToBit(Symbol[0]); Color curColor = bPic.GetPixel(0, 0); BitArray tempArray = ByteToBit(curColor.R); tempArray[0] = ArrBeginSymbol[0]; tempArray[1] = ArrBeginSymbol[1]; byte nR = BitToByte(tempArray); tempArray = ByteToBit(curColor.G); tempArray[0] = ArrBeginSymbol[2]; tempArray[1] = ArrBeginSymbol[3]; tempArray[2] = ArrBeginSymbol[4]; byte nG = BitToByte(tempArray); tempArray = ByteToBit(curColor.B); tempArray[0] = ArrBeginSymbol[5]; tempArray[1] = ArrBeginSymbol[6]; tempArray[2] = ArrBeginSymbol[7]; byte nB = BitToByte(tempArray); Color nColor = Color.FromArgb(nR, nG, nB); bPic.SetPixel(0, 0, nColor); WriteCountText(CountText, bPic); //записываем количество символов исходного текста int index = 0; bool st = false; for (int i = 4; i < bPic.Width; i++) { for (int j = 0; j < bPic.Height; j++) { Color pixelColor = bPic.GetPixel(i, j); if (index == bList.Count) { st = true; break; } BitArray colorArray = ByteToBit(pixelColor.R); BitArray messageArray = ByteToBit(bList[index]); colorArray[0] = messageArray[0]; //меняем colorArray[1] = messageArray[1]; // в нашем цвете биты byte newR = BitToByte(colorArray); colorArray = ByteToBit(pixelColor.G); colorArray[0] = messageArray[2]; colorArray[1] = messageArray[3]; colorArray[2] = messageArray[4]; byte newG = BitToByte(colorArray); colorArray = ByteToBit(pixelColor.B); colorArray[0] = messageArray[5]; colorArray[1] = messageArray[6]; colorArray[2] = messageArray[7]; byte newB = BitToByte(colorArray); Color newColor = Color.FromArgb(newR, newG, newB); bPic.SetPixel(i, j, newColor); index ++; } if (st) { break; } }
И, соответственно код, который считывает информацию из bmp файла
Bitmap bPic = new Bitmap(rFile); if (!isEncryption(bPic)) { MessageBox.Show("В файле нет зашифрованной информации", "Информация", MessageBoxButtons.OK); return; } int countSymbol = ReadCountText(bPic); //считали количество символов byte[] message = new byte[countSymbol]; int index = 0; bool st = false; for (int i = 4; i < bPic.Width; i++) { for (int j = 0; j < bPic.Height; j++) { Color pixelColor = bPic.GetPixel(i, j); if (index == message.Length) { st = true; break; } BitArray colorArray = ByteToBit(pixelColor.R); BitArray messageArray = ByteToBit(pixelColor.R); ; messageArray[0] = colorArray[0]; messageArray[1] = colorArray[1]; colorArray = ByteToBit(pixelColor.G); messageArray[2] = colorArray[0]; messageArray[3] = colorArray[1]; messageArray[4] = colorArray[2]; colorArray = ByteToBit(pixelColor.B); messageArray[5] = colorArray[0]; messageArray[6] = colorArray[1]; messageArray[7] = colorArray[2]; message[index] = BitToByte(messageArray); index++; } if (st) { break; } } string strMessage = Encoding.GetEncoding(1251).GetString(message);
Заключение
Метод LSB не сложен в реализации, и его можно использовать для сокрытия нужной информации в bmp файле. Но существенным недостатком метода является то, что размер bmp имеет большой размер, что делает этот метод нежизнеспособным для передачи конфиденциальной информации по сети интернет.
Сам проект: github.com/Finnetrib/StegProject
Картинка без текста drive.google.com/open?id=1yhUrJYGasCl2UAPgkO6kinFAK55GS4jW
Картинка с текстом drive.google.com/open?id=1aHmy_gTIcJRLr1fz-ehBmxSmg3MQfQkw
