Steganografia in GIF

Introduzione

Salve.
Non molto tempo fa, mentre studiavo all'università, avevo un progetto sul tema 'Metodi di protezione delle informazioni software'. Il compito era quello di sviluppare un programma per inserire messaggi nei file in formato GIF. Ho deciso di farlo in Java.

In questo articolo descriverò alcuni aspetti teorici e come è stato creato questo piccolo programma.

Parte teorica

Formato GIF

GIF (Graphics Interchange Format) è un formato per lo scambio di immagini, capace di memorizzare dati compressi senza perdita di qualità in una palette fino a 256 colori. Questo formato è stato sviluppato nel 1987 (GIF87a) da CompuServe per la trasmissione di immagini raster attraverso le reti. Nel 1989, il formato è stato modificato (GIF89a) introducendo il supporto per la trasparenza e l'animazione.

I file in formato GIF hanno una struttura a blocchi. Questi blocchi hanno sempre una lunghezza fissa (o una lunghezza che dipende da alcuni flag), quindi è praticamente impossibile sbagliare nel determinare dove si trovi ciascun blocco. La struttura della più semplice GIF immagine non animata in formato GIF89a:

Steganografia in GIF

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

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

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 i messaggi nei file immagine saranno:

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

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

In questo metodo verranno utilizzati 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'incorporazione del messaggio, ciascuna componente del colore cambierà al massimo di 3/255 gradazioni. Questa modifica sarà, da un lato, invisibile o difficile da percepire per l'occhio umano e, dall'altro, non sarà distinguibile su dispositivi di output di 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 di messaggio nella componente di ciascun colore, la lunghezza massima del messaggio (con la palette massima nell'immagine) è di 192 byte. Dopo l'incorporazione del messaggio nell'immagine, la dimensione del file non cambia.

Metodo di estensione della palette, funzionante solo per la struttura GIF. Sarà più efficace con immagini a palette di dimensioni ridotte. Il suo principio consiste nell'aumentare la dimensione della palette, fornendo così spazio aggiuntivo per registrare i byte necessari nei byte di colore. Considerando che la dimensione minima della palette è di 2 colori (6 byte), la dimensione massima del messaggio incorporato può essere 256×3–6=762 byte. Lo svantaggio è la bassa crittografia; il messaggio incorporato può essere letto con qualsiasi editor di testo, a meno che il messaggio non sia stato sottoposto a crittografia aggiuntiva.

Parte pratica

Progettazione del programma

Tutti gli strumenti necessari per implementare 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 consente di lavorare con array di bit, così come 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.

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

L'implementazione degli algoritmi sarà fornita 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 sequenza di bit, che il decrittore legge per primo e verifica la correttezza. Se non corrisponde, si considera che nell'immagine non ci sia alcun messaggio nascosto. Successivamente, è necessario indicare la lunghezza del messaggio. Poi viene fornito il testo del messaggio stesso.

Diagramma delle classi dell'intera applicazione:

Steganografia in GIF

Implementazione del programma

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

Esaminiamo la classe GIFEncryptorByLSBMethod.

Steganografia in GIF

Campi firstLSBit e secondLSBit contengono i numeri di bit di ogni byte dell'immagine, in cui devono essere scritti e da cui deve essere letto il messaggio. Il campo checkSequence memorizza la sequenza di controllo dei bit per garantire il riconoscimento del messaggio integrato. 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");
	}
	
	// legge 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 UnableToEncodeException("Il file di input ha un formato GIF errato");
	}
	
	// legge la proprietà della dimensione della palette dai 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;
	
	// scrive 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++;
	}
	
	// scrive 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++;
	}
	
	// scrive 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++;
		}
	}
	
	// scrive 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();
	
	// controlla 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 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 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 c'è alcun messaggio criptato 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 decifrato è inferiore a 0");
	}
	if (possibleTextLength < textLength) {
		throw new UnableToDecodeException("Non ci sono messaggi (La lunghezza del messaggio decifrato (" + textLength + ") è inferiore alla 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à simile, ma differisce 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 una finestra di dialogo per la selezione dei file, mostrando messaggi di errore, ecc.; oltre ad altri metodi: openImage(), che consente all'utente di selezionare un'immagine, exit(), che esegue l'uscita dall'applicazione. Questi metodi vengono chiamati da Actiondei relativi elementi di 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 risiede nel modo in cui vengono scritti e letti i byte del messaggio dalla palette.

Funzionamento del programma

Il metodo LBS

Supponiamo di avere un'immagine così:

Steganografia in GIF

In questa immagine, la palette contiene 256 colori (come conserva Paint). I primi quattro colori: bianco, nero, rosso, verde. Gli altri colori sono neri. La sequenza di bit della palette 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 poco dall'originale.

Originale
Immagine con messaggio nascosto

Steganografia in GIF
Steganografia in GIF

Metodo di estensione della palette

Aprendo l'immagine in cui è stato inserito il messaggio secondo questo metodo, si può riscontrare la seguente situazione:

Steganografia in GIF

È evidente che per un'adeguata attività di spionaggio, questo metodo non risulta efficace e potrebbe richiedere ulteriore crittografia del messaggio.

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

Fonti utilizzate:

Scarica:

Fonte: habr.com

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