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:

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:

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:

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.

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:

Ș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:

@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:

Î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…

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

![]()
Metoda de extindere a paletei
Deschizând imaginea în care a fost plasat mesajul prin această metodă, se poate observa următoarea imagine:

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
