Steganografie în GIF

Introducere

Salut.
Nu demult, când eram la universitate, am avut o lucrare la disciplina „Metode software de protecție a informațiilor”. Conform sarcinii, trebuia să realizez un program care să încorporeze un mesaj în fișiere de format GIF. Am decis să lucrez în Java.

În acest articol voi descrie câteva aspecte teoretice, precum și modul în care a fost creat acest mic program.

Partea teoretică

Formatul GIF

GIF (eng. Graphics Interchange Format — format pentru schimbul de imagini) este un format de stocare a imaginilor grafice, capabil să stocheze date comprimate fără pierderi de calitate într-un format de până la 256 de culori. Acest format a fost dezvoltat în 1987 (GIF87a) de compania CompuServe pentru transmiterea imaginilor raster prin rețele. În 1989, formatul a fost modificat (GIF89a), adăugând suport pentru transparență și animație.

Fișierele de format GIF au o structură bloc. Aceste blocuri au întotdeauna o lungime fixă (sau depind de anumite semnale), astfel că este practic imposibil să te înșeli în ceea ce privește locul în care se află fiecare bloc. Structura unei imagini GIF neanimate de format GIF89a este:

Steganografie în GIF

Dintre toate blocurile structurii, în acest caz ne vor interesa blocul paletei globale și parametrii care răspund de paletă:

  • CT — existența paletei globale. Dacă acest semnal este activat, paleta globală ar trebui să înceapă imediat după descriptorul ecranului logic.
  • Size — dimensiunea paletei și numărul de culori ale imaginii. Valorile acestui parametru sunt:

Size
Numărul de culori
Dimensiunea paletei, octeți

7
256
768

6
128
384

5
64
192

4
32
96

3
16
48

2
8
24

1
4
12

0
2
6

Metode de criptare

Ca metode de criptare a mesajelor în fișierele imaginii se vor folosi:

  • Metoda LSB (Least Significant Bit, cel mai puțin semnificativ bit)
  • Metoda completării paletei

Metoda LSB — o metodă comună de steganografie. Aceasta constă în înlocuirea celor mai puțin semnificativi biți din container (în cazul nostru, octeții paletei globale) cu biții mesajului ascuns.

Programul va utiliza în cadrul acestei metode ultimele două biți din octeții paletei globale. Aceasta înseamnă că pentru o imagine de 24 de biți, unde culoarea paletei reprezintă trei octeți pentru roșu, albastru și verde, după inserarea mesajului, fiecare componentă de culoare va fi modificată cu maxim 3/255 din nuanțele sale. Această modificare va fi, în primul rând, imperceptibilă sau greu perceptibilă pentru ochiul uman și, în al doilea rând, nu va fi vizibilă pe dispozitive de ieșire de calitate mai slabă.

Cantitatea de informație va depinde direct de dimensiunea paletei imaginii. Deoarece dimensiunea maximă a paletei este de 256 de culori și, dacă se înregistrează câte două biți de mesaj în fiecare componentă de culoare, lungimea maximă a mesajului (în cazul paletei maxime în imagine) este de 192 de octeți. După inserarea mesajului în imagine, dimensiunea fișierului nu se schimbă.

Metoda de extindere a paletei, care funcționează doar pentru structura GIF. Aceasta va fi cea mai eficientă în imagini cu palete de dimensiuni mici. Esența acesteia constă în faptul că mărește dimensiunea paletei, oferind astfel un spațiu suplimentar pentru a înregistra biții necesari în locul biților de culoare. Având în vedere că dimensiunea minimă a paletei este de 2 culori (6 octeți), dimensiunea maximă a mesajului inserat poate fi 256×3–6=762 de octeți. Dezavantajul este o criptare scăzută, mesajul inserat putând fi citit cu ajutorul oricărui editor de text, dacă mesajul nu a fost supus unei criptări suplimentare.

Partea practică

Proiectarea programului

Toate instrumentele necesare pentru implementarea algoritmilor de criptare și decriptare vor fi disponibile în pachetul com.tsarik.steganography. Acest pachet include interfața Encryptor cu metodele encrypt și decrypt, clasa Binary, care oferă posibilitatea de a lucra cu array-uri de biți, și, de asemenea, clasele de excepție UnableToEncryptException și UnableToDecryptException, care trebuie utilizate în metodele interfeței Encryptor în caz de erori de codare și decodare, respectiv.

Pachetul principal al programului com.tsarik.programs.gifed va include clasa executabilă a programului cu metoda statică main, care permite lansarea programului; o clasă care stochează parametrii programului; și pachete cu alte clase.

Implementarea algoritmilor va fi prezentată în pachet com.tsarik.programs.gifed.gif clasele GIFEncryptorByLSBMethod și GIFEncryptorByPaletteExtensionMethod. Ambele clase vor implementa interfața Encryptor.

Pe baza structurii formatului GIF, se poate elabora un algoritm general pentru integrarea unui mesaj în paleta imaginii:

Steganografie în GIF

Pentru a determina prezența mesajului în imagine, este necesar să se adauge la începutul mesajului o anumită secvență de biți, pe care deșifratorul o citește prima dată și o verifică pentru corectitudine. Dacă aceasta nu se potrivește, se consideră că în imagine nu există niciun mesaj ascuns. Apoi trebuie specificată lungimea mesajului. După aceea, textul mesajului însuși.

Diagrama claselor aplicației:

Steganografie în GIF

Implementarea programului

Implementarea întregului program poate fi împărțită în două componente: implementarea metodelor de criptare și de decriptare a interfeței Encryptor, în clasele GIFEncryptorByLSBMethod și GIFEncryptorByPaletteExtensionMethod, și implementarea interfeței utilizatorului.

Să examinăm clasa GIFEncryptorByLSBMethod.

Steganografie în GIF

Câmpuri firstLSBit și secondLSBit conțin numerele biților fiecărui octet de imagine în care mesajul trebuie să fie stocat și din care trebuie citit. Câmpul checkSequence stochează secvența de biți de control pentru a asigura recunoașterea mesajului încorporat. Metoda statică getEncryptingFileParameters returnează parametrii fișierului specificat și caracteristicile mesajului potențial.

Algoritmul metodei encrypt clasei GIFEncryptorByLSBMethod:

Steganografie în GIF

Și codul său:

@Override
public void encrypt(File in, File out, String text) throws UnableToEncodeException, NullPointerException, IOException {
	if (in == null) {
		throw new NullPointerException("Fișierul de intrare este nul");
	}
	if (out == null) {
		throw new NullPointerException("Fișierul de ieșire este nul");
	}
	if (text == null) {
		throw new NullPointerException("Textul este nul");
	}
	
	// citire bytes din fișierul de intrare
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// verificare format
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToEncodeException("Fișierul de intrare are un format GIF greșit");
	}
	
	// citire proprietatea dimensiunii paletei din primele trei biți ai celui de-al 10-lea byte din fișier
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// calculare număr de culori și lungimea mesajului posibil
	int bOrigColorCount = (int)Math.pow(2, bsize+1);
	int possibleMessageLength = bOrigColorCount*3/4;
	int possibleTextLength = possibleMessageLength-2; // un byte pentru verificare și un byte pentru lungimea mesajului
	
	if (possibleTextLength < text.length()) {
		throw new UnableToEncodeException("Textul este prea mare");
	}
	
	int n = 13;
	
	// scriere secvență de verificare
	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++;
	}
	
	// scriere lungime text
	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++;
	}
	
	// scriere mesaj
	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++;
		}
	}
	
	// scriere fișier de ieșire
	OutputStream os = new FileOutputStream(out);
	os.write(bytes);
	os.close();
}

Algoritmul și codul sursă al metodei decrypt clasei GIFEncryptorByLSBMethod:

Steganografie în GIF

@Override
public String decrypt(File in) throws UnableToDecodeException, NullPointerException, IOException {
	if (in == null) {
		throw new NullPointerException("Fișierul de intrare este null");
	}
	
	// citirea byte-urilor din fișierul de intrare
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// verificarea formatului
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToDecodeException("Fișierul de intrare are un format GIF greșit");
	}
	
	// citirea proprietății dimensiunii paletei din primele trei biți din al 10-lea byte din fișier
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// calcularea numărului de culori și a lungimii posibile a mesajului
	int bOrigColorCount = (int)Math.pow(2, bsize+1);
	int possibleMessageLength = bOrigColorCount*3/4;
	int possibleTextLength = possibleMessageLength-2;	// un byte pentru verificare și un byte pentru lungimea mesajului
	
	int n = 13;
	
	// citirea secvenței de verificare
	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("Nu există un mesaj criptat în imagine (Secvența de verificare este greșită)");
	}
	
	// citirea lungimii textului
	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("Lungimea textului decodat este mai mică decât 0");
	}
	if (possibleTextLength < textLength) {
		throw new UnableToDecodeException("Nu există mesaje (Lungimea mesajului decodat (" + textLength + ") este mai mică decât lungimea posibilă a mesajului (" + possibleTextLength + "))");
	}
	
	// citirea biților textului și generarea byte-urilor textului
	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);
}

Implementarea clasei GIFEncryptorByPaletteExtensionMethod va fi similară, dar se diferențiază prin metoda de salvare/încărcare a informațiilor.

În clasa MainFrame sunt descrise metodele „wrapper”: encryptImage(Encryptor encryptor) și decryptImage(Encryptor encryptor), care procesează rezultatele metodelor interfeței Encryptor și realizează interacțiunea cu utilizatorul, adică deschid dialoguri de selectare a fișierelor, afișează mesaje de eroare etc.; precum și alte metode: openImage(), care oferă utilizatorului posibilitatea de a alege o imagine, exit(), care iese din aplicație. Aceste metode sunt apelate din Acțiuneopțiunile corespunzătoare ale meniului. În această clasă sunt, de asemenea, implementate metode auxiliare: createComponents() — crearea componentelor formularului, loadImageFile(File f) — încărcarea imaginii într-o componentă specială din fișier. Implementarea clasei GIFEncryptorByPaletteExtensionMethod este similară cu implementarea clasei GIFEncryptorByLSBMethod, principala diferență constând în metoda de scriere și citire a byte-urilor mesajului din paletă.

Funcționarea programului

Metoda LBS

Să presupunem că există o astfel de imagine:

Steganografie în GIF

În această imagine, paleta constă din 256 de culori (așa cum păstrează Paint). Primele patru culori: alb, negru, roșu, verde. Celelalte culori sunt negre. Secvența de biți a paletei globale va fi următoarea:

11111111 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000…

Steganografie în GIF

După introducerea mesajului, biții subliniați vor fi înlocuiți cu biți din mesaj. Imaginea obținută va fi aproape identică cu originalul.

Original
Imagine cu un mesaj introdus

Steganografie în GIF
Steganografie în GIF

Metoda de extindere a paletei

Deschizând imaginea în care a fost plasat mesajul prin această metodă, se poate observa următoarea imagine:

Steganografie în GIF

Este evident că pentru o activitate de spionaj completă, această metodă nu este adecvată și poate necesita o criptare suplimentară a mesajului.

Criptarea/decriptarea în imagini animate funcționează la fel ca în imaginile statice obișnuite, fără a afecta animația.

Sursele utilizate:

Descarcă:

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster