Introduktion
Hej.
For ikke så længe siden, da jeg studerede på universitetet, havde jeg et kursusprojekt i emnet "Softwaremetoder til informationsbeskyttelse". Opgaven krævede, at jeg lavede et program, der indlejrede beskeder i GIF-filer. Jeg besluttede at lave det i Java.
I denne artikel vil jeg beskrive nogle teoretiske punkter samt, hvordan dette lille program blev skabt.
Teoretisk del
GIF-format
GIF (engelsk. Graphics Interchange Format — format til udveksling af billeder) er et format til opbevaring af grafiske billeder, der kan gemme komprimerede data uden tab af kvalitet i et format med op til 256 farver. Dette format blev udviklet i 1987 (GIF87a) af CompuServe til transmission af rasterbilleder over netværk. I 1989 blev formatet modifiseret (GIF89a), hvor der blev tilføjet støtte til gennemsigtighed og animation.
GIF-filer har en blokstruktur. Disse blokke har altid en fast længde (eller længden afhænger af nogle flags), så det er praktisk talt umuligt at tage fejl af, hvor hvilken blok befinder sig. Strukturen af det simpleste ikke-animerede GIF-billede i GIF89a-formatet:

Af alle blokke i strukturen vil vi i dette tilfælde være interesseret i den globale palette og parametrene, der vedrører paletten:
CT— tilstedeværelsen af en global palette. Hvis dette flag er sat, skal den globale palette begynde lige efter beskrivelsen af den logiske skærm.Størrelse— størrelsen på paletten og antallet af farver i billedet. Værdierne for denne parameter:
Størrelse
Antal farver
Størrelse på paletten, byte
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
Som metoder til kryptering af beskeder i billedfiler vil der blive anvendt:
- LSB-metode (Least Significant Bit, mindste signifikante bit)
- Metode til at tilføje til paletten
LSB-metode — en almindelig metode inden for steganografi. Den går ud på at erstatte de sidste signifikante bits i containeren (i vores tilfælde bytes i den globale palette) med bits fra den skjulte besked.
Programmet vil anvende de to sidste bits i bytes af den globale palette inden for denne metode. Det betyder, at for et 24-bit billede, hvor farven i paletten består af tre bytes til rød, blå og grøn, vil hver farvekomponent ændre sig med maksimalt 3/255 gradationer efter indlejringen af beskeden. Sådan en ændring vil for det første være usynlig eller svær at opdage for det menneskelige øje, og for det andet vil den ikke kunne adskilles på lavkvalitets output-enheder.
Mængden af information vil direkte afhænge af størrelsen på billedpaletten. Da den maksimale størrelse på paletten er 256 farver, og hvis man skriver to bits af beskeden ind i hver farvekomponent, så vil den maksimale længde af beskeden (med den maksimale palette i billedet) være 192 bytes. Efter indlejringen af beskeden i billedet vil filstørrelsen forblive uændret.
Paletteudvidelsesmetoden, som kun fungerer for GIF-strukturen. Den vil være mest effektiv i billeder med små paletter. Dens essens er, at den øger størrelsen på paletten, hvilket giver ekstra plads til at skrive de nødvendige bytes på farvebytes' placering. På baggrund af at den minimale størrelse på paletten er 2 farver (6 bytes), kan den maksimale størrelse af den indlejrede besked være 256×3–6=762 bytes. Ulempen er lav krypteringsbeskyttelse; den indlejrede besked kan læses med enhver teksteditor, medmindre beskeden er blevet udsat for yderligere kryptering.
Praktisk del
Programdesign
Alle nødvendige værktøjer til implementeringen af krypterings- og dekrypteringsalgoritmer vil befinde sig i pakken com.tsarik.steganography. Denne pakke inkluderer grænsefladen Encryptor med metoderne krypter og decrypt, klassen Binary, der giver mulighed for at arbejde med bit-arrays, samt undtagelsesklasserne UnableToEncryptException og UnableToDecryptException, der skal anvendes i metoderne i grænsefladen Encryptor i tilfælde af fejl ved kodning og dekodning henholdsvis.
Hovedpakken i programmet com.tsarik.programs.gifed vil inkludere den kørbare klasse til programmet med en statisk metode main, der gør det muligt at starte programmet; en klasse, der gemmer programmets parametre; og pakker med andre klasser.
Implementeringen af selve algoritmerne vil blive præsenteret i pakken com.tsarik.programs.gifed.gif klasserne GIFEncryptorByLSBMethod og GIFEncryptorByPaletteExtensionMethod. Begge disse klasser vil implementere interface Encryptor.
Baseret på strukturen af GIF-formatet kan man lave en generel algoritme til at indsætte beskeder i billedets palette:

For at bestemme tilstedeværelsen af en besked i billedet, skal man tilføje en bestemt sekvens af bits i starten af beskeden, som dekoderen aflæser først og kontrollerer for korrekthed. Hvis det ikke matcher, anses det for, at der ikke er nogen skjult besked i billedet. Derefter skal længden af beskeden angives. Herefter selve teksten af beskeden.
Diagrammet over klasserne i hele applikationen:

Implementeringen af programmet
Implementeringen af hele programmet kan opdeles i to dele: implementeringen af krypterings- og dekrypteringsmetoderne i interface Encryptor, i klasserne GIFEncryptorByLSBMethod og GIFEncryptorByPaletteExtensionMethod, og implementeringen af brugergrænsefladen.
Lad os se på klassen GIFEncryptorByLSBMethod.

Felter firstLSBit og secondLSBit indeholder nummeret på bits for hver byte i billedet, hvor beskeden skal indlægges og hvor den skal aflæses. Feltet checkSequence gemmer kontrolsekvensen af bits for at sikre genkendelsen af den indbyggede besked. Den statiske metode getEncryptingFileParameters returnerer parametrene for den angivne fil og karakteristika for den potentielle besked.
Algoritmen for metoden krypter klassen GIFEncryptorByLSBMethod:

Og dens 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");
}
// læs bytes fra inputfil
byte[] bytes = new byte[(int)in.length()];
InputStream is = new FileInputStream(in);
is.read(bytes);
is.close();
// tjek format
if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
throw new UnableToEncodeException("Input file has wrong GIF format");
}
// læs paletstørrelse egenskab fra de første tre bits i den 10. byte fra filen
byte[] b10 = Binary.toBitArray(bytes[10]);
byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
// beregn farvetælling og mulig meddelelseslængde
int bOrigColorCount = (int)Math.pow(2, bsize+1);
int possibleMessageLength = bOrigColorCount*3/4;
int possibleTextLength = possibleMessageLength-2; // en byte til check og en byte til meddelelseslængde
if (possibleTextLength < text.length()) {
throw new UnableToEncodeException("Text is too big");
}
int n = 13;
// skriv kontrolsekvens
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++;
}
// skriv tekstlængde
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++;
}
// skriv meddelelse
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++;
}
}
// skriv outputfil
OutputStream os = new FileOutputStream(out);
os.write(bytes);
os.close();
}
Algoritme og kildekode for metoden decrypt klassen GIFEncryptorByLSBMethod:

@Override
public String decrypt(File in) throws UnableToDecodeException, NullPointerException, IOException {
if (in == null) {
throw new NullPointerException("Input file is null");
}
// læs bytes fra inputfilen
byte[] bytes = new byte[(int)in.length()];
InputStream is = new FileInputStream(in);
is.read(bytes);
is.close();
// tjek format
if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
throw new UnableToDecodeException("Input file has wrong GIF format");
}
// læs paletstørrelsesegenskab fra de første tre bits i den 10. byte fra filen
byte[] b10 = Binary.toBitArray(bytes[10]);
byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
// beregn farvetælling og mulig beskedlængde
int bOrigColorCount = (int)Math.pow(2, bsize + 1);
int possibleMessageLength = bOrigColorCount * 3 / 4;
int possibleTextLength = possibleMessageLength - 2; // en byte for tjek og en byte for beskedlængde
int n = 13;
// læs tjeksekvens
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("Der er ingen krypteret besked i billedet (Tjeksekvens er forkert)");
}
// læs tekstlængde
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("Dekodet tekstlængde er mindre end 0");
}
if (possibleTextLength < textLength) {
throw new UnableToDecodeException("Der er ingen beskeder (Dekodet beskedlængde (" + textLength + ") er mindre end mulig beskedlængde (" + possibleTextLength + "))");
}
// læs tekstbits og lav tekstbytes
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);
}
Implementeringen af klassen GIFEncryptorByPaletteExtensionMethod vil være ens, men adskiller sig i metoden til at gemme/læse oplysninger.
I klassen MainFrame er metoderne "wrapper" beskrevet: encryptImage(Encryptor encryptor) og decryptImage(Encryptor encryptor), der behandler resultaterne af interface-metoderne Encryptor og interagerer med brugeren, dvs. åbner dialoger til filvalg, viser fejlmeddelelser osv.; samt andre metoder: openImage(), der giver brugeren mulighed for at vælge et billede, exit(), der afslutter applikationen. Disse metoder kaldes fra Handlingde respektive menupunkter. I denne klasse er yderligere hjælpefunktioner blevet implementeret: createComponents() — oprettelse af formkomponenter, loadImageFile(File f) — indlæsning af billede i en specialkomponent fra fil. Implementeringen af klassen GIFEncryptorByPaletteExtensionMethod er ens med implementeringen af klassen GIFEncryptorByLSBMethod, den primære forskel er måden at skrive og læse bytes af beskeden fra paletten.
Programmets drift
Metoden LBS
Lad os antage, at der er et sådant billede:

I dette billede består paletten af 256 farver (som Paint gemmer). De første fire farver: hvid, sort, rød, grøn. De øvrige farver er sorte. Sekvensen af bits i den globale palette vil være som følgende:
11111111 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000…

Efter implementeringen vil de understregede bits blive erstattet af bits fra meddelelsen. Det resulterende billede adskiller sig næsten ikke fra originalen.
Original
Billede med indsat meddelelse

![]()
Paletteudvidelsesmetoden
Når man åbner billedet, hvori meddelelsen er placeret ved denne metode, kan man finde et sådant billede:

Det er klart, at denne metode ikke er egnet til fuldstændig spionage og måske kræver en yderligere kryptering af meddelelsen.
Kryptering/afkryptering i animerede billeder fungerer som i almindelige statiske billeder, og animationen påvirkes ikke.
Brugte kilder:
Download:
Kilde: habr.com
