Esteganografía en GIF

Introdución

Saúdos.
Non hai moito tempo, cando estudaba na universidade, había un curso na disciplina "Métodos de software de seguridade da información". A tarefa requiriunos crear un programa que incorporase unha mensaxe en ficheiros GIF. Decidín facelo en Java.

Neste artigo describirei algúns puntos teóricos, así como como se creou este pequeno programa.

Parte teórica

formato GIF

GIF (Graphics Interchange Format - un formato para intercambiar imaxes) é un formato para almacenar imaxes gráficas, capaz de almacenar datos comprimidos sen perda de calidade nun formato de ata 256 cores. Este formato foi desenvolvido en 1987 (GIF87a) por CompuServe para transmitir imaxes ráster a través de redes. En 1989, o formato foi modificado (GIF89a), engadiuse soporte para a transparencia e a animación.

Os ficheiros GIF teñen unha estrutura de bloques. Estes bloques sempre teñen unha lonxitude fixa (ou depende dalgunhas bandeiras), polo que é case imposible equivocarse sobre onde se atopa cada bloque. A estrutura da imaxe GIF non animada máis sinxela en formato GIF89a:

Esteganografía en GIF

De todos os bloques da estrutura, neste caso interesaranos o bloque de paleta global e os parámetros responsables da paleta:

  • CT — presenza dunha paleta global. Se se establece esta marca, a paleta global debe comezar inmediatamente despois do identificador da pantalla lóxica.
  • Size - tamaño da paleta e número de cores da imaxe. Valores deste parámetro:

tamaño
Número de cores
Tamaño da paleta, 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

Métodos de cifrado

Usaranse os seguintes métodos para cifrar mensaxes en ficheiros de imaxe:

  • Método LSB (bit menos significativo).
  • Método de adición de paletas

Método LSB - un método común de esteganografía. Consiste en substituír os últimos bits significativos do contedor (no noso caso, os bytes da paleta global) polos bits da mensaxe oculta.

O programa usará os dous últimos bits dos bytes da paleta global como parte deste método. Isto significa que para unha imaxe de 24 bits, onde a paleta de cores é de tres bytes para vermello, azul e verde, despois de incorporar unha mensaxe nela, cada compoñente de cor cambiará nun máximo de 3/255 gradacións. Tal cambio, en primeiro lugar, será invisible ou difícil de notar para o ollo humano e, en segundo lugar, non será visible en dispositivos de saída de información de baixa calidade.

A cantidade de información dependerá directamente do tamaño da paleta de imaxes. Dado que o tamaño máximo da paleta é de 256 cores, e se se escriben dous bits de mensaxe no compoñente de cada cor, a lonxitude máxima da mensaxe (coa paleta máxima na imaxe) é de 192 bytes. Unha vez que a mensaxe está incrustada na imaxe, o tamaño do ficheiro non cambia.

Método de expansión da paleta, que só funciona para a estrutura GIF. Será máis eficaz en imaxes cunha paleta pequena. A súa esencia é que aumenta o tamaño da paleta, proporcionando así espazo adicional para escribir os bytes necesarios en lugar dos bytes de cor. Se consideramos que o tamaño mínimo da paleta é de 2 cores (6 bytes), entón o tamaño máximo da mensaxe incrustada pode ser de 256 × 3–6 = 762 bytes. A desvantaxe é a baixa seguridade criptográfica; a mensaxe incorporada pódese ler mediante calquera editor de texto se a mensaxe non foi sometida a un cifrado adicional.

Parte práctica

Deseño do programa

No paquete incluiranse todas as ferramentas necesarias para implementar algoritmos de cifrado e descifrado com.tsarik.steganography. Este paquete inclúe a interface Encryptor con métodos encrypt и decrypt, Clase Binary, que ofrece a posibilidade de traballar con matrices de bits, así como con clases de excepción UnableToEncryptException и UnableToDecryptException, que debería usarse nos métodos de interface Encryptor en caso de erros de codificación e descodificación respectivamente.

Paquete principal do programa com.tsarik.programs.gifed incluirá unha clase de programa executable cun método estático main, que lle permite executar o programa; unha clase que almacena os parámetros do programa; e paquetes con outras clases.

A implementación dos propios algoritmos presentarase no paquete com.tsarik.programs.gifed.gif clases GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod. Ambas estas clases implementarán a interface Encryptor.

Baseándose na estrutura do formato GIF, pode crear un algoritmo xeral para introducir unha mensaxe na paleta de imaxes:

Esteganografía en GIF

Para determinar a presenza dunha mensaxe nunha imaxe, é necesario engadir unha determinada secuencia de bits ao comezo da mensaxe, que o descodificador le primeiro e comproba a corrección. Se non coincide, considérase que non hai ningunha mensaxe oculta na imaxe. A continuación, cómpre especificar a lonxitude da mensaxe. Despois o texto da propia mensaxe.

Diagrama de clases de toda a aplicación:

Esteganografía en GIF

Implementación do programa

A implementación de todo o programa pódese dividir en dous compoñentes: implementación de métodos de cifrado da interface e descifrado Encryptor, nas clases GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod, e a implementación da interface de usuario.

Considere a clase GIFEncryptorByLSBMethod.

Esteganografía en GIF

campos firstLSBit и secondLSBit conteñen os números de bits de cada byte da imaxe na que se debe introducir a mensaxe e desde onde se debe ler a mensaxe. Campo checkSequence almacena unha secuencia de bits de control para garantir o recoñecemento da mensaxe incorporada. Método estático getEncryptingFileParameters devolve os parámetros do ficheiro especificado e as características da mensaxe potencial.

Algoritmo do método encrypt clase GIFEncryptorByLSBMethod:

Esteganografía en GIF

E o seu código:

@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();
}

Algoritmo e código fonte do método decrypt clase GIFEncryptorByLSBMethod:

Esteganografía en GIF

@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);
}

Implementación da clase GIFEncryptorByPaletteExtensionMethod será semellante, só o método de gardar/ler a información é diferente.

En clase MainFrame Descríbense os métodos de envoltura: encryptImage(Encryptor encryptor) и decryptImage(Encryptor encryptor), procesando os resultados dos métodos de interface Encryptor e interactuar co usuario, é dicir, abrir un diálogo de selección de ficheiros, mostrar mensaxes de erro, etc.; así como outros métodos: openImage(), permitindo ao usuario seleccionar unha imaxe, exit(), que sae da aplicación. Estes métodos chámanse de Actionelementos do menú correspondentes. Esta clase implementa adicionalmente métodos auxiliares: createComponents() - creación de compoñentes de formulario, loadImageFile(File f) — cargar unha imaxe nun compoñente especial dun ficheiro. Implementación da clase GIFEncryptorByPaletteExtensionMethod similar á implementación da clase GIFEncryptorByLSBMethod, a principal diferenza está na forma en que se escriben e len os bytes da mensaxe desde a paleta.

Funcionamento do programa

Método LBS

Digamos que hai unha imaxe coma esta:

Esteganografía en GIF

Nesta imaxe, a paleta consta de 256 cores (como garda Paint). As catro primeiras cores son: branco, negro, vermello, verde. Outras cores son negras. A secuencia global de bits da paleta será a seguinte:

11111111 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000...

Esteganografía en GIF

Unha vez incorporada a mensaxe, os bits subliñados substituiranse polos bits da mensaxe. A imaxe resultante case non é diferente da orixinal.

Orixinal
Imaxe con mensaxe incrustada

Esteganografía en GIF
Esteganografía en GIF

Método de expansión da paleta

Cando abras unha imaxe que contén unha mensaxe usando este método, verás a seguinte imaxe:

Esteganografía en GIF

Está claro que este método non funcionará para actividades de espionaxe completas e pode requirir un cifrado adicional da mensaxe.

O cifrado/descifrado en imaxes animadas funciona igual que nas imaxes estáticas normais, pero a animación non se rompe.

Fontes utilizadas:

Descargar:

Fonte: www.habr.com

Engadir un comentario