Доброго времени суток!
На Хабрахабре уже есть статьи на данную тему и я хочу поделится своим вариантом решения данной задачи. И так, начнем.
Общие сведения
Стеганографическая система (стегосистема) — объединение методов и средств используемых для создания скрытого канала для передачи информации. Стеганография в наше время часто применяется, как правило, для встраивания цифровых водяных знаков, являющееся основой для систем защиты авторских прав и 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 имеет большой размер, что делает этот метод нежизнеспособным для передачи конфиденциальной информации по сети интернет.
Сам проект: rghost.ru/37128613.
В архиве 2 картинки, одна с текстом, вторая без.
Автор: Fenja
Спасибо! Как раз сейчас занимаюсь это темой. Содержательная статья, очень помогла.
Очень интересная статья, но при попытке работы с другим изображением и текстом выдает ошибку выхода за границы массива. Ка сделать, чтобы она работала с любыми изображениями и текстом?