Vær hilset.
For ikke så længe siden, da jeg studerede på universitetet, var der et kursus i disciplinen "Softwaremetoder til informationssikkerhed." Opgaven krævede, at vi lavede et program, der indlejrede en besked i GIF-filer. Jeg besluttede at gøre det i Java.
I denne artikel vil jeg beskrive nogle teoretiske pointer, samt hvordan dette lille program blev til.
Teoretisk del
GIF-format
GIF (Graphics Interchange Format - et format til udveksling af billeder) er et format til lagring af grafiske billeder, der er i stand til at gemme komprimerede data uden tab af kvalitet i et format på op til 256 farver. Dette format blev udviklet i 1987 (GIF87a) af CompuServe til transmission af rasterbilleder over netværk. I 1989 blev formatet ændret (GIF89a), understøttelse af gennemsigtighed og animation blev tilføjet.
GIF-filer har en blokstruktur. Disse blokke har altid en fast længde (eller det afhænger af nogle flag), så det er næsten umuligt at lave en fejl om, hvor hver blok er placeret. Strukturen af det enkleste ikke-animerede GIF-billede i GIF89a-format:
Af alle strukturens blokke vil vi i dette tilfælde være interesserede i den globale paletblok og de parametre, der er ansvarlige for paletten:
CT — tilstedeværelsen af en global palet. Hvis dette flag er indstillet, skal den globale palet begynde umiddelbart efter det logiske skærmhåndtag.
Size — paletstørrelse og antal farver på billedet. Værdier for denne parameter:
Størrelse
Antal farver
Paletstørrelse, bytes
7
256
768
6
128
384
5
64
192
4
32
96
3
16
48
2
8
24
1
4
12
0
2
6
Krypteringsmetoder
Følgende metoder vil blive brugt til at kryptere meddelelser i billedfiler:
LSB-metoden (Least Significant Bit).
Palettetilsætningsmetode
LSB metode - en almindelig metode til steganografi. Det består i at erstatte de sidste signifikante bits i containeren (i vores tilfælde de globale paletbytes) med bits af den skjulte meddelelse.
Programmet vil bruge de sidste to bits i de globale paletbytes som en del af denne metode. Det betyder, at for et 24-bit billede, hvor farvepaletten er tre bytes for rød, blå og grøn, efter indlejring af en besked i det, vil hver farvekomponent ændre sig med maksimalt 3/255 gradueringer. En sådan ændring vil for det første være usynlig eller svær at bemærke for det menneskelige øje, og for det andet vil den ikke være synlig på informationsoutputenheder af lav kvalitet.
Mængden af information vil direkte afhænge af størrelsen af billedpaletten. Da palettens maksimale størrelse er 256 farver, og hvis to beskedbits er skrevet ind i komponenten af hver farve, så er den maksimale beskedlængde (med den maksimale palet i billedet) 192 bytes. Når meddelelsen er indlejret i billedet, ændres filstørrelsen ikke.
Paletteudvidelsesmetode, som kun virker for GIF-strukturen. Det vil være mest effektivt på billeder med en lille palet. Dens essens er, at den øger palettens størrelse og giver derved ekstra plads til at skrive de nødvendige bytes i stedet for farvebytes. Hvis vi overvejer, at palettens minimumsstørrelse er 2 farver (6 bytes), så kan den maksimale størrelse af den indlejrede meddelelse være 256 × 3-6 = 762 bytes. Ulempen er lav kryptografisk sikkerhed; den indlejrede besked kan læses ved hjælp af en hvilken som helst teksteditor, hvis beskeden ikke er blevet udsat for yderligere kryptering.
Praktisk del
Program design
Alle nødvendige værktøjer til implementering af krypterings- og dekrypteringsalgoritmer vil være inkluderet i pakken com.tsarik.steganography. Denne pakke inkluderer grænsefladen Encryptor med metoder encrypt и decrypt, Klasse Binary, som giver mulighed for at arbejde med bit-arrays, såvel som undtagelsesklasser UnableToEncryptException и UnableToDecryptException, som skal bruges i grænseflademetoder Encryptor i tilfælde af henholdsvis indkodnings- og afkodningsfejl.
Hovedprogrampakke com.tsarik.programs.gifed vil inkludere en kørebar programklasse med en statisk metode main, så du kan køre programmet; en klasse, der gemmer programparametre; og pakker med andre klasser.
Implementeringen af selve algoritmerne vil blive præsenteret i pakken com.tsarik.programs.gifed.gif klasser GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod. Begge disse klasser vil implementere grænsefladen Encryptor.
Baseret på strukturen af GIF-formatet kan du oprette en generel algoritme til at introducere en besked i billedpaletten:
For at bestemme tilstedeværelsen af en meddelelse i et billede er det nødvendigt at tilføje en bestemt sekvens af bit til begyndelsen af meddelelsen, som dekoderen læser først og kontrollerer for korrekthed. Hvis det ikke matcher, så anses det for, at der ikke er nogen skjult besked i billedet. Dernæst skal du angive længden af meddelelsen. Derefter selve beskedens tekst.
Klassediagram over hele applikationen:
Implementering af programmet
Implementeringen af hele programmet kan opdeles i to komponenter: implementering af grænsefladekryptering og dekrypteringsmetoder Encryptor, i klasserne GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod, og implementeringen af brugergrænsefladen.
Overvej klassen GIFEncryptorByLSBMethod.
felter firstLSBit и secondLSBit indeholde antallet af bits af hver byte af billedet, som meddelelsen skal indtastes i, og hvorfra meddelelsen skal læses. Mark checkSequence gemmer en tjekbitsekvens for at sikre genkendelse af den indlejrede meddelelse. Statisk metode getEncryptingFileParameters returnerer parametrene for den angivne fil og egenskaberne for den potentielle besked.
Metode algoritme encrypt klasse GIFEncryptorByLSBMethod:
Og hans kode:
@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();
}
Metodens algoritme og kildekode decrypt klasse 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);
}
Gennemførelse af klassen GIFEncryptorByPaletteExtensionMethod vil være ens, kun metoden til at gemme/læse information er anderledes.
I klassen MainFrame indpakningsmetoder er beskrevet: encryptImage(Encryptor encryptor) и decryptImage(Encryptor encryptor), behandling af resultaterne af grænseflademetoder Encryptor og interagere med brugeren, dvs. åbne en filvalgsdialog, vise fejlmeddelelser, etc.; samt andre metoder: openImage(), der giver brugeren mulighed for at vælge et billede, exit(), som afslutter applikationen. Disse metoder kaldes fra Actions tilsvarende menupunkter. Denne klasse implementerer desuden hjælpemetoder: createComponents() - oprettelse af formularkomponenter, loadImageFile(File f) — indlæsning af et billede i en speciel komponent fra en fil. Gennemførelse af klassen GIFEncryptorByPaletteExtensionMethod svarende til klasseimplementeringen GIFEncryptorByLSBMethod, er den største forskel i den måde, meddelelsesbytes skrives og læses fra paletten.
Program drift
LBS metode
Lad os sige, at der er et billede som dette:
På dette billede består paletten af 256 farver (som Paint gemmer). De første fire farver er: hvid, sort, rød, grøn. Andre farver er sorte. Den globale palette bitsekvens vil være som følger:
Når meddelelsen er indlejret, vil de understregede bits blive erstattet med bits fra meddelelsen. Det resulterende billede er næsten ikke anderledes end originalen.
Original
Billede med indlejret besked
Paletteudvidelsesmetode
Når du åbner et billede, der indeholder en besked ved hjælp af denne metode, vil du se følgende billede:
Det er klart, at denne metode ikke vil fungere for fuldgyldige spionageaktiviteter og kan kræve yderligere kryptering af beskeden.
Kryptering/dekryptering i animerede billeder fungerer ligesom i almindelige statiske billeder, men animationen er ikke brudt.