Въведение
Здравейте.
Неотдавна, когато учех в университета, имах курсова работа по дисциплината „Програмни методи за защита на информацията“. По задание трябваше да направя програма, внедряваща съобщение във файлове с формат GIF. Реших да го направя на Java.
В тази статия ще опиша някои теоретични моменти, както и как беше създадена тази малка програма.
Теоретична част
GIF формат
GIF (от англ. Graphics Interchange Format — формат за обмен на изображения) — формат за съхранение на графични изображения, способен да съхранява компресирани данни без загуба на качество в формат до 256 цвята. Този формат бе разработен през 1987 година (GIF87a) от фирмата CompuServe за предаване на растрови изображения по мрежи. През 1989 година форматът бе модифициран (GIF89a), като бяха добавени поддръжка за прозрачност и анимация.
GIF файловете имат блокова структура. Данните във блоковете винаги имат фиксирана дължина (или тя зависи от някои флагове), така че грешката при определянето на кои блокове са разположени, е практически невъзможна. Структурата на най-простото неанимирано GIF изображение с формат GIF89a:

От всички блокове в структурата в този случай ще се интересуваме от блока на глобалната палитра и параметри, отговорни за палитрата:
CT— наличие на глобална палитра. Ако този флаг е зададен, то веднага след дескриптора на логичния екран трябва да започне глобалната палитра.Size— размер на палитрата и брой цветове на картината. Стойностите на този параметър:
Size
Брой цветове
Размер на палитрата, байтове
7
256
768
6
128
384
5
64
192
4
32
96
3
16
48
2
8
24
1
4
12
0
2
6
Методи за шифруване
Като методи за шифриране на съобщения в изображен файлов формат ще се използват:
- Метод LSB (Least Significant Bit, най-малко значим бит)
- Метод за допълване на палитрата
Метод LSB — разпространен метод на стеганография. Той се състои в замяната на последните значими битове в контейнера (в нашия случай байтовете на глобалната палитра) с битовете на скриваното съобщение.
В програмата ще се използват последните два бита в байтовете на глобалната палитра в рамките на този метод. Това означава, че за 24-битово изображение, където цветът на палитрата представлява три байта за червено, синьо и зелено, след вграждането на съобщението, всяка цветна съставка ще се промени с максимум 3/255 градации. Тази промяна, първо, ще бъде незабележима или трудно забележима за човешкото око, а второ, няма да бъде различима на нискокачествени устройства за извеждане на информация.
Количеството информация ще зависи пряко от размера на палитрата на изображението. Тъй като максималният размер на палитрата е 256 цвята и ако се записват по два бита съобщение във всяка цветна съставка, максималната дължина на съобщението (при максимална палитра в изображението) е 192 байта. След вграждането на съобщението в изображението, размерът на файла не се променя.
Метод на разширяване на палитрата, който работи само за структурата GIF. Той ще бъде най-ефективен в изображения с малки палитри. Същността му е, че увеличава размера на палитрата, давайки допълнително пространство за запис на необходимите байтове вместо байтовете на цветовете. Като се има предвид, че минималният размер на палитрата е 2 цвята (6 байта), максималният размер на вграденото съобщение може да бъде 256×3–6=762 байта. Недостатъкът е ниската криптозащита, вграденото съобщение може да бъде прочетено с помощта на всеки текстов редактор, ако не е било подложено на допълнително криптиране.
Практическа част
Проектиране на програмата
Всички необходими инструменти за реализиране на алгоритмите за криптиране и декриптиране ще се намират в пакета com.tsarik.steganography. Този пакет включва интерфейс Encryptor с методи encrypt и decrypt, клас Binary, предоставящ възможност за работа с масиви от битове, а също така класове за изключения UnableToEncryptException и UnableToDecryptException, които трябва да се използват в методите на интерфейса Encryptor в случай на грешки при кодиране и декодиране съответно.
Основният пакет на програмата com.tsarik.programs.gifed ще включва стартиращ клас на програмата със статичен метод main, позволяващ запуск на програмата; клас, съхраняващ параметрите на програмата; и пакети с други класове.
Реализация на самите алгоритми ще бъде представена в пакета com.tsarik.programs.gifed.gif класовете GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod. И двата класа ще реализират интерфейса Encryptor.
На базата на структурата на GIF формата може да се изготви общ алгоритъм за вграждане на съобщение в палитрата на изображението:

За определяне на присъствието на съобщение в изображението е необходимо в началото на съобщението да се добави определена последователност от битове, която декодерът прочита първоначално и проверява за коректност. Ако тя не съвпада, то се счита, че в изображението няма скрито съобщение. След това трябва да се посочи дължината на съобщението. След това самият текст на съобщението.
Диаграмата на класовете на цялото приложение:

Реализация на програмата
Реализацията на цялата програма може да се разбие на две съставни части: реализация на методите за криптиране и декриптиране на интерфейса Encryptor, в класовете GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod, и реализация на потребителския интерфейс.
Нека разгледаме класа GIFEncryptorByLSBMethod.

Полета firstLSBit и secondLSBit съдържат номера на битовете на всеки байт от изображението, в които трябва да се вмъква и от които трябва да се прочита съобщението. Полето checkSequence съхранява контролна последователност от битове за осигуряване на разпознаване на вграденото съобщение. Статичният метод getEncryptingFileParameters върща параметрите на указан файл и характеристиките на потенциалното съобщение.
Алгоритъм на метода encrypt клас GIFEncryptorByLSBMethod:

И неговият код:
@Override
public void encrypt(File in, File out, String text) throws UnableToEncodeException, NullPointerException, IOException {
if (in == null) {
throw new NullPointerException("Input file is null");
}
if (out == null) {
throw new NullPointerException("Output file is null");
}
if (text == null) {
throw new NullPointerException("Text is null");
}
// read bytes from input file
byte[] bytes = new byte[(int)in.length()];
InputStream is = new FileInputStream(in);
is.read(bytes);
is.close();
// check format
if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
throw new UnableToEncodeException("Input file has wrong GIF format");
}
// read palette size property from first three bits in the 10-th byte from the file
byte[] b10 = Binary.toBitArray(bytes[10]);
byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
// calculate color count and possible message length
int bOrigColorCount = (int)Math.pow(2, bsize+1);
int possibleMessageLength = bOrigColorCount*3/4;
int possibleTextLength = possibleMessageLength-2; // one byte for check and one byte for message length
if (possibleTextLength < text.length()) {
throw new UnableToEncodeException("Text is too big");
}
int n = 13;
// write check sequence
for (int i = 0; i < checkSequence.length/2; i++) {
byte[] ba = Binary.toBitArray(bytes[n]);
ba[firstLSBit] = checkSequence[2*i];
ba[secondLSBit] = checkSequence[2*i+1];
bytes[n] = Binary.toByte(ba);
n++;
}
// write text length
byte[] cl = Binary.toBitArray((byte)text.length());
for (int i = 0; i < cl.length/2; i++) {
byte[] ba = Binary.toBitArray(bytes[n]);
ba[firstLSBit] = cl[2*i];
ba[secondLSBit] = cl[2*i+1];
bytes[n] = Binary.toByte(ba);
n++;
}
// write message
byte[] textBytes = text.getBytes();
for (int i = 0; i < textBytes.length; i++) {
byte[] c = Binary.toBitArray(textBytes[i]);
for (int ci = 0; ci < c.length/2; ci++) {
byte[] ba = Binary.toBitArray(bytes[n]);
ba[firstLSBit] = c[2*ci];
ba[secondLSBit] = c[2*ci+1];
bytes[n] = Binary.toByte(ba);
n++;
}
}
// write output file
OutputStream os = new FileOutputStream(out);
os.write(bytes);
os.close();
}
Алгоритъмът и изходният код на метода decrypt клас GIFEncryptorByLSBMethod:

@Override
public String decrypt(File in) throws UnableToDecodeException, NullPointerException, IOException {
if (in == null) {
throw new NullPointerException("Input file is null");
}
// read bytes from input file
byte[] bytes = new byte[(int)in.length()];
InputStream is = new FileInputStream(in);
is.read(bytes);
is.close();
// check format
if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
throw new UnableToDecodeException("Input file has wrong GIF format");
}
// read palette size property from first three bits in the 10-th byte from the file
byte[] b10 = Binary.toBitArray(bytes[10]);
byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
// calculate color count and possible message length
int bOrigColorCount = (int)Math.pow(2, bsize+1);
int possibleMessageLength = bOrigColorCount*3/4;
int possibleTextLength = possibleMessageLength-2; // one byte for check and one byte for message length
int n = 13;
// read check sequence
byte[] csBits = new byte[checkSequence.length];
for (int i = 0; i < 4; i++) {
byte[] ba = Binary.toBitArray(bytes[n]);
csBits[2*i] = ba[firstLSBit];
csBits[2*i+1] = ba[secondLSBit];
n++;
}
byte cs = Binary.toByte(csBits);
if (cs != Binary.toByte(checkSequence)) {
throw new UnableToDecodeException("There is no encrypted message in the image (Check sequence is incorrect)");
}
// read text length
byte[] cl = new byte[8];
for (int i = 0; i < 4; i++) {
byte[] ba = Binary.toBitArray(bytes[n]);
cl[2*i] = ba[firstLSBit];
cl[2*i+1] = ba[secondLSBit];
n++;
}
byte textLength = Binary.toByte(cl);
if (textLength < 0) {
throw new UnableToDecodeException("Decoded text length is less than 0");
}
if (possibleTextLength < textLength) {
throw new UnableToDecodeException("There is no messages (Decoded message length (" + textLength + ") is less than Possible message length (" + possibleTextLength + "))");
}
// read text bits and make text bytes
byte[] bt = new byte[textLength];
for (int i = 0; i < bt.length; i++) {
byte[] bc = new byte[8];
for (int bci = 0; bci < bc.length/2; bci++) {
byte[] ba = Binary.toBitArray(bytes[n]);
bc[2*bci] = ba[firstLSBit];
bc[2*bci+1] = ba[secondLSBit];
n++;
}
bt[i] = Binary.toByte(bc);
}
return new String(bt);
}
Имплементация на класа GIFEncryptorByPaletteExtensionMethod ще бъде аналогична, само ще се различава методът за запазване/четене на информация.
В класа MainFrame са описани методи-«обертки»: encryptImage(Encryptor encryptor) и decryptImage(Encryptor encryptor), обработващи резултатите от методите на интерфейса Encryptor и осъществяващи взаимодействие с потребителя, т.е. отварят диалог за избор на файлове, показват съобщения за грешки и т.н.; както и други методи: openImage(), даващ възможност на потребителя да избере изображение, exit(), осъществяващ изход от приложението. Тези методи се извикват от Actionсъответните елементи на менюто. В този клас допълнително са реализирани помощни методи: createComponents() — създаване на компонентите на формуляра, loadImageFile(File f) — зареждане на изображение в специален компонент от файл. Имплементацията на класа GIFEncryptorByPaletteExtensionMethod е аналогична на имплементацията на класа GIFEncryptorByLSBMethod, основната разлика е в начина на запис и четене на байтове от съобщението от палитрата.
Работа на програмата
Метод LBS
Да предположим, че има такова изображение:

В това изображение палитрата се състои от 256 цвята (както запазва Paint). Първите четири цвята: бяло, черно, червено, зелено. Останалите цветове са черни. Последователността на битовете в глобалната палитра ще бъде следната:
11111111 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000…

След внедряването на съобщението, подчертаните битове ще бъдат заменени с битове от съобщението. Полученото изображение почти не се различава от оригинала.
Оригинал
Изображение с внедрено съобщение

![]()
Метод на разширяване на палитрата
Като отворите изображението, в което е поставено съобщението по този метод, може да се открие такава картина:

Разбира се, че за пълноценна шпионска дейност този метод не става и изисква, може би, допълнително шифриране на съобщението.
Шифрирането/дешифрирането в анимирани изображения работи, както и при обикновените статични изображения, при което анимацията не се нарушава.
Използвани източници:
Свали:
Източник: habr.com
