Pozdrav.
Ne tako davno, dok sam studirao na sveučilištu, postojao je kolegij iz discipline "Softverske metode informacijske sigurnosti". Zadatak je od nas zahtijevao izradu programa koji ugrađuje poruku u GIF datoteke. Odlučio sam to učiniti u Javi.
U ovom ću članku opisati neke teorijske točke, kao i kako je nastao ovaj mali program.
Teorijski dio
GIF format
GIF (Graphics Interchange Format - format za razmjenu slika) je format za pohranjivanje grafičkih slika, sposoban za pohranu komprimiranih podataka bez gubitka kvalitete u formatu do 256 boja. Ovaj je format 1987. (GIF87a) razvio CompuServe za prijenos rasterskih slika preko mreža. Godine 1989. format je modificiran (GIF89a), dodana je podrška za transparentnost i animaciju.
GIF datoteke imaju blok strukturu. Ovi blokovi uvijek imaju fiksnu duljinu (ili to ovisi o nekim oznakama), tako da je gotovo nemoguće pogriješiti gdje se koji blok nalazi. Struktura najjednostavnije neanimirane GIF slike u formatu GIF89a:
Od svih blokova strukture, u ovom slučaju će nas zanimati blok globalne palete i parametri odgovorni za paletu:
CT — prisutnost globalne palete. Ako je ova zastavica postavljena, globalna paleta mora započeti odmah nakon logičkog rukovatelja zaslona.
Size — veličina palete i broj boja na slici. Vrijednosti za ovaj parametar:
Veličina
Broj boja
Veličina palete, bajtovi
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 šifriranja
Za šifriranje poruka u slikovnim datotekama koristit će se sljedeće metode:
LSB (Least Significant Bit) metoda
Metoda dodavanja palete
LSB metoda - uobičajena metoda steganografije. Sastoji se od zamjene zadnjih značajnih bitova u spremniku (u našem slučaju, bajtova globalne palete) s bitovima skrivene poruke.
Program će koristiti zadnja dva bita u bajtovima globalne palete kao dio ove metode. To znači da će se za 24-bitnu sliku, gdje je paleta boja tri bajta za crvenu, plavu i zelenu, nakon ugrađivanja poruke u nju, svaka komponenta boje promijeniti za najviše 3/255 stupnjeva. Takva će promjena, prvo, biti nevidljiva ili teško uočljiva ljudskom oku, a drugo, neće biti vidljiva na uređajima za izlaz informacija niske kvalitete.
Količina informacija izravno će ovisiti o veličini palete slika. Budući da je maksimalna veličina palete 256 boja, a ako su dva bita poruke upisana u komponentu svake boje, tada je maksimalna duljina poruke (s maksimalnom paletom na slici) 192 bajta. Nakon što je poruka ugrađena u sliku, veličina datoteke se ne mijenja.
Metoda proširenja palete, koji radi samo za GIF strukturu. Najučinkovitije će biti na slikama s malom paletom. Njegova bit je da povećava veličinu palete, čime se osigurava dodatni prostor za upisivanje potrebnih bajtova umjesto bajtova boja. Ako uzmemo u obzir da je minimalna veličina palete 2 boje (6 bajtova), tada maksimalna veličina ugrađene poruke može biti 256 × 3–6 = 762 bajta. Nedostatak je niska kriptografska sigurnost; ugrađena poruka se može čitati pomoću bilo kojeg uređivača teksta ako poruka nije podvrgnuta dodatnoj enkripciji.
Praktični dio
Dizajn programa
Svi potrebni alati za implementaciju algoritama enkripcije i dešifriranja bit će uključeni u paket com.tsarik.steganography. Ovaj paket uključuje sučelje Encryptor s metodama encrypt и decrypt, Razred Binary, koji pruža mogućnost rada s nizovima bitova, kao i klasama izuzetaka UnableToEncryptException и UnableToDecryptException, koji bi se trebao koristiti u metodama sučelja Encryptor u slučaju grešaka kodiranja odnosno dekodiranja.
Glavni programski paket com.tsarik.programs.gifed će uključivati klasu programa koja se može izvoditi sa statičkom metodom main, omogućujući vam pokretanje programa; klasa koja pohranjuje parametre programa; i paketi s drugim razredima.
U paketu će biti predstavljena implementacija samih algoritama com.tsarik.programs.gifed.gif klase GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod. Obje ove klase će implementirati sučelje Encryptor.
Na temelju strukture GIF formata možete stvoriti opći algoritam za uvođenje poruke u paletu slika:
Za utvrđivanje prisutnosti poruke u slici potrebno je na početak poruke dodati određeni niz bitova koje dekoder prvo čita i provjerava ispravnost. Ako se ne podudara, smatra se da na slici nema skrivene poruke. Zatim morate odrediti duljinu poruke. Zatim sam tekst poruke.
Dijagram klasa cijele aplikacije:
Provedba programa
Implementacija cjelokupnog programa može se podijeliti u dvije komponente: implementacija enkripcije sučelja i metode dešifriranja Encryptor, u razredima GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod, te implementacija korisničkog sučelja.
Razmotrite klasu GIFEncryptorByLSBMethod.
Polja firstLSBit и secondLSBit sadrže brojeve bitova svakog bajta slike u koju treba unijeti poruku i odakle poruku treba pročitati. Polje checkSequence pohranjuje niz bitova za provjeru kako bi se osiguralo prepoznavanje ugrađene poruke. Statička metoda getEncryptingFileParameters vraća parametre navedene datoteke i karakteristike potencijalne poruke.
Algoritam metode encrypt razred GIFEncryptorByLSBMethod:
I njegov kod:
@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();
}
Algoritam i izvorni kod metode decrypt razred 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);
}
Implementacija klase GIFEncryptorByPaletteExtensionMethod bit će slični, samo je način spremanja/čitanja informacija drugačiji.
U klasi MainFrame opisane su metode omotača: encryptImage(Encryptor encryptor) и decryptImage(Encryptor encryptor), obrada rezultata metoda sučelja Encryptor i interakcija s korisnikom, tj. otvaranje dijaloga za odabir datoteke, prikazivanje poruka o pogreškama, itd.; kao i druge metode: openImage(), dopuštajući korisniku odabir slike, exit(), koji izlazi iz aplikacije. Ove metode se pozivaju iz Actionodgovarajuće stavke izbornika. Ova klasa dodatno implementira pomoćne metode: createComponents() - stvaranje komponenti forme, loadImageFile(File f) — učitavanje slike u posebnu komponentu iz datoteke. Implementacija klase GIFEncryptorByPaletteExtensionMethod slično implementaciji klase GIFEncryptorByLSBMethod, glavna razlika je u načinu na koji se bajtovi poruke pišu i čitaju iz palete.
Rad programa
LBS metoda
Recimo da postoji ovakva slika:
Na ovoj slici, paleta se sastoji od 256 boja (koliko Paint sprema). Prve četiri boje su: bijela, crna, crvena, zelena. Ostale boje su crne. Globalna sekvenca bitova palete bit će sljedeća: