Steganografia in GIF

Introduzione

Salve.
Non molto tempo fa, mentre studiavo all'università, ho dovuto fare un progetto per il corso di "Metodi di programmazione per la protezione delle informazioni". Il compito prevedeva di creare un programma che integrasse messaggi nei file in formato GIF. Ho deciso di farlo in Java.

In questo articolo descriverò alcuni aspetti teorici e come è stata creata questa piccola applicazione.

Parte teorica

Formato GIF

GIF (abbreviazione di Graphics Interchange Format) è un formato per lo scambio di immagini, in grado di memorizzare dati compressi senza perdita di qualità in un massimo di 256 colori. Questo formato è stato sviluppato nel 1987 (GIF87a) da CompuServe per la trasmissione di immagini raster sulle reti. Nel 1989, il formato è stato modificato (GIF89a) con l'aggiunta del supporto per la trasparenza e l'animazione.

I file nel formato GIF hanno una struttura a blocchi. I dati di questi blocchi hanno sempre una lunghezza fissa (o possono dipendere da determinati flag), quindi è praticamente impossibile sbagliare sulla posizione di ciascun blocco. La struttura di una semplice immagine GIF non animata nel formato GIF89a:

Steganografia in GIF

Tra tutti i blocchi della struttura, in questo caso ci interesseranno il blocco della palette globale e i parametri associati alla palette:

  • CT — presenza della palette globale. Se questo flag è impostato, subito dopo il descrittore dello schermo logico deve iniziare la palette globale.
  • Dimensione — dimensione della palette e numero di colori dell'immagine. I valori di questo parametro sono:

Dimensione
Numero di colori
Dimensione della palette, 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

Metodi di crittografia

I metodi utilizzati per crittografare messaggi nei file immagine saranno:

  • Metodo LSB (Least Significant Bit, bit meno significativo)
  • Metodo di integrazione della palette

Metodo LSB — metodo di steganografia molto comune. Si basa sulla sostituzione degli ultimi bit significativi nel contenitore (nel nostro caso i byte della palette globale) con i bit del messaggio nascosto.

Nel programma verranno utilizzati nell'ambito di questo metodo gli ultimi due bit nei byte della palette globale. Ciò significa che per un'immagine a 24 bit, dove il colore della palette è rappresentato da tre byte per i colori rosso, blu e verde, dopo l'inserimento del messaggio, ciascuna componente del colore cambierà al massimo di 3/255 gradazioni. Tale modifica, da un lato, sarà invisibile o difficilmente percepibile dall'occhio umano, e dall'altro, non sarà distinguibile su dispositivi di output a bassa qualità.

La quantità di informazioni dipenderà direttamente dalle dimensioni della palette dell'immagine. Poiché la dimensione massima della palette è di 256 colori e, se si registrano due bit del messaggio in ciascuna componente del colore, la lunghezza massima del messaggio (con la palette massima nell'immagine) è di 192 byte. Dopo l'inserimento del messaggio nell'immagine, la dimensione del file non cambia.

Metodo di estensione della palette, funzionante solo per la struttura GIF. Sarà particolarmente efficace nelle immagini con palette di piccole dimensioni. La sostanza consiste nel fatto che aumenta la dimensione della palette, fornendo così spazio aggiuntivo per registrare i byte necessari al posto dei byte dei colori. Considerando che la dimensione minima della palette è di 2 colori (6 byte), la dimensione massima del messaggio inserito può essere 256×3–6=762 byte. Lo svantaggio è la bassa crittografia, il messaggio inserito può essere letto con qualsiasi editor di testo, se non è stato sottoposto a ulteriore crittografia.

Parte pratica

Progettazione del programma

Tutti gli strumenti necessari per l'implementazione degli algoritmi di crittografia e decifratura saranno contenuti nel pacchetto com.tsarik.steganography. Questo pacchetto include l'interfaccia Encryptor con i metodi encrypt e decrypt, la classe Binary, che fornisce la possibilità di lavorare con array di bit, e inoltre le classi di eccezione UnableToEncryptException e UnableToDecryptException, che devono essere utilizzate nei metodi dell'interfaccia Encryptor in caso di errori di codifica e decodifica rispettivamente.

Il pacchetto principale del programma com.tsarik.programs.gifed includerà la classe eseguibile del programma con il metodo statico main, che consente di avviare il programma; una classe che conserva i parametri del programma; e pacchetti con altre classi.

L'implementazione degli algoritmi stessi sarà presentata nel pacchetto com.tsarik.programs.gifed.gif classi GIFEncryptorByLSBMethod e GIFEncryptorByPaletteExtensionMethod. Entrambe queste classi implementeranno l'interfaccia Encryptor.

Sulla base della struttura del formato GIF, è possibile elaborare un algoritmo generale per l'inserimento di un messaggio nella palette dell'immagine:

Steganografia in GIF

Per determinare la presenza del messaggio nell'immagine, è necessario aggiungere all'inizio del messaggio una certa sequenza di bit, che il decoder legge per primo e verifica la correttezza. Se non corrisponde, si considera che nell'immagine non ci sia un messaggio nascosto. Successivamente, è necessario indicare la lunghezza del messaggio. Poi il testo stesso del messaggio.

Diagramma delle classi dell'intera applicazione:

Steganografia in GIF

Implementazione del programma

L'implementazione dell'intero programma può essere suddivisa in due componenti: l'implementazione dei metodi di crittografia e decrittografia dell'interfaccia Encryptor, nelle classi GIFEncryptorByLSBMethod e GIFEncryptorByPaletteExtensionMethod, e l'implementazione dell'interfaccia utente.

Consideriamo la classe GIFEncryptorByLSBMethod.

Steganografia in GIF

Campi firstLSBit e secondLSBit contenere i numeri dei bit di ciascun byte dell'immagine, in cui deve essere inserito e da cui deve essere letto il messaggio. Il campo checkSequence memorizza la sequenza di controllo dei bit per garantire il riconoscimento del messaggio incorporato. Il metodo statico getEncryptingFileParameters restituisce i parametri del file specificato e le caratteristiche del messaggio potenziale.

L'algoritmo del metodo encrypt classe GIFEncryptorByLSBMethod:

Steganografia in GIF

E il suo codice:

@Override
public void encrypt(File in, File out, String text) throws UnableToEncodeException, NullPointerException, IOException {
	if (in == null) {
		throw new NullPointerException("Il file di input è nullo");
	}
	if (out == null) {
		throw new NullPointerException("Il file di output è nullo");
	}
	if (text == null) {
		throw new NullPointerException("Il testo è nullo");
	}
	
	// leggi i byte dal file di input
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// controlla il formato
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToEncodeException("Il file di input ha un formato GIF errato");
	}
	
	// leggi la dimensione della palette dalla proprietà dei primi tre bit nel decimo byte del file
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// calcola il numero di colori e la possibile lunghezza del messaggio
	int bOrigColorCount = (int)Math.pow(2, bsize + 1);
	int possibleMessageLength = bOrigColorCount * 3 / 4;
	int possibleTextLength = possibleMessageLength - 2; // un byte per il controllo e un byte per la lunghezza del messaggio
	
	if (possibleTextLength < text.length()) {
		throw new UnableToEncodeException("Il testo è troppo lungo");
	}
	
	int n = 13;
	
	// scrivi la sequenza di controllo
	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++;
	}
	
	// scrivi la lunghezza del testo
	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++;
	}
	
	// scrivi il messaggio
	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++;
		}
	}
	
	// scrivi il file di output
	OutputStream os = new FileOutputStream(out);
	os.write(bytes);
	os.close();
}

Algoritmo e codice sorgente del metodo decrypt classe GIFEncryptorByLSBMethod:

Steganografia in GIF

@Override
public String decrypt(File in) throws UnableToDecodeException, NullPointerException, IOException {
	if (in == null) {
		throw new NullPointerException("Il file di input è nullo");
	}
	
	// leggi i byte dal file di input
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// verifica il formato
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToDecodeException("Il file di input ha un formato GIF errato");
	}
	
	// leggi la dimensione della tavolozza dalla terza parte nei primi dieci byte del file
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// calcola il conteggio dei colori e la lunghezza possibile del messaggio
	int bOrigColorCount = (int)Math.pow(2, bsize+1);
	int possibleMessageLength = bOrigColorCount*3/4;
	int possibleTextLength = possibleMessageLength-2;	// un byte per il controllo e un byte per la lunghezza del messaggio
	
	int n = 13;
	
	// leggi la sequenza di controllo
	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("Non ci sono messaggi crittografati nell'immagine (la sequenza di controllo è errata)");
	}
	
	// leggi la lunghezza del testo
	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("La lunghezza del testo decodificato è minore di 0");
	}
	if (possibleTextLength < textLength) {
		throw new UnableToDecodeException("Non ci sono messaggi (la lunghezza del messaggio decodificato (" + textLength + ") è minore della lunghezza possibile del messaggio (" + possibleTextLength + "))");
	}
	
	// leggi i bit di testo e crea i byte di testo
	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);
}

Implementazione della classe GIFEncryptorByPaletteExtensionMethod sarà analoga, differente solo nel metodo di salvataggio / lettura delle informazioni.

Nella classe MainFrame sono descritti i metodi "wrapper": encryptImage(Encryptor encryptor) e decryptImage(Encryptor encryptor), che elaborano i risultati dei metodi dell'interfaccia Encryptor e interagiscono con l'utente, ad esempio aprendo un dialogo per la selezione dei file, mostrando messaggi di errore, ecc.; e anche altri metodi: openImage(), che consente all'utente di scegliere un'immagine, exit(), che esegue l'uscita dall'applicazione. Questi metodi sono chiamati dai Azione'di corrispondenti voci del menu. In questa classe sono implementati anche metodi ausiliari: createComponents() — creazione dei componenti del modulo, loadImageFile(File f) — caricamento dell'immagine in un componente speciale da un file. L'implementazione della classe GIFEncryptorByPaletteExtensionMethod è analoga all'implementazione della classe GIFEncryptorByLSBMethod, la principale differenza sta nel modo di scrivere e leggere i byte del messaggio dalla tavolozza.

Funzionamento del programma

Metodo LBS

Supponiamo di avere un'immagine come questa:

Steganografia in GIF

In questa immagine, la tavolozza è composta da 256 colori (come mantiene Paint). I primi quattro colori sono: bianco, nero, rosso, verde. Gli altri colori sono neri. La sequenza dei bit della tavolozza globale sarà la seguente:

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

Steganografia in GIF

Dopo l'inserimento del messaggio, i bit sottolineati verranno sostituiti dai bit del messaggio. L'immagine risultante differisce di poco dall'originale.

Originale
Immagine con messaggio incorporato

Steganografia in GIF
Steganografia in GIF

Metodo di estensione della palette

Aprendo l'immagine in cui è stato inserito il messaggio con questo metodo, si può osservare la seguente scena:

Steganografia in GIF

È evidente che per un'attività spionistica completa questo metodo non è praticabile e richiede, forse, una crittografia aggiuntiva del messaggio.

La crittografia/decrittografia nelle immagini animate funziona come nelle normali immagini statiche, senza compromettere l'animazione.

Fonti utilizzate:

Scarica:

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS - ProHoster