GIF のステガノグラフィー

導入

ようこそ。
少し前まで、私が大学で学んでいたとき、「情報セキュリティのソフトウェア手法」という分野の授業がありました。 この課題では、GIF ファイルにメッセージを埋め込むプログラムを作成する必要がありました。 Javaでやることにしました。

この記事では、いくつかの理論的なポイントと、この小さなプログラムがどのように作成されたかについて説明します。

理論部分

GIF形式

GIF (Graphics Interchange Format - 画像交換用の形式) はグラフィック画像を保存するための形式で、品質を損なうことなく圧縮データを最大 256 色の形式で保存できます。 この形式 (GIF1987a) は、ネットワーク上でラスター イメージを送信するために CompuServe によって 87 年に開発されました。 1989 年に形式が変更され (GIF89a)、透明性とアニメーションのサポートが追加されました。

GIF ファイルはブロック構造になっています。 これらのブロックは常に固定長であるため (または一部のフラグに依存します)、各ブロックの位置を間違えることはほとんどありません。 GIF89a 形式の最も単純な非アニメーション GIF 画像の構造は次のとおりです。

GIF のステガノグラフィー

構造体のすべてのブロックのうち、この場合はグローバル パレット ブロックとパレットを担当するパラメーターに注目します。

  • CT — グローバルパレットの存在。 このフラグが設定されている場合、グローバル パレットは論理スクリーン ハンドルの直後から開始する必要があります。
  • Size — パレットのサイズと画像内の色の数。 このパラメータの値:

サイズ
色の数
パレットのサイズ、バイト数

7
256
768

6
128
384

5
64
192

4
32
96

3
16
48

2
8
24

1
4
12

0
2
6

暗号化方式

画像ファイル内のメッセージを暗号化するには、次の方法が使用されます。

  • LSB(最下位ビット)方式
  • パレットの追加方法

LSB法 - ステガノグラフィーの一般的な方法。 これは、コンテナ内の最後の重要なビット (この場合、グローバル パレット バイト) を隠しメッセージのビットに置き換えることで構成されます。

プログラムは、このメソッドの一部としてグローバル パレット バイトの最後の 24 ビットを使用します。 これは、カラーパレットが赤、青、緑の 3 バイトである 255 ビット画像の場合、メッセージを埋め込むと、各色成分が最大 XNUMX/XNUMX 階調で変化することを意味します。 このような変化は、第一に、人間の目には見えないか、気づきにくいものであり、第二に、低品質の情報出力デバイスでは見えません。

情報量は画像パレットのサイズに直接依存します。 パレットの最大サイズは 256 色であり、各色のコンポーネントに 192 つのメッセージ ビットが書き込まれる場合、最大メッセージ長 (画像内の最大パレットの場合) は XNUMX バイトになります。 メッセージが画像に埋め込まれると、ファイル サイズは変わりません。

パレット展開方法、これは GIF 構造に対してのみ機能します。 パレットが小さい画像で最も効果的です。 その本質は、パレットのサイズを大きくし、それによってカラー バイトの代わりに必要なバイトを書き込むための追加スペースを提供することです。 パレットの最小サイズが 2 色 (6 バイト) であると考えると、埋め込みメッセージの最大サイズは 256 × 3–6 = 762 バイトになります。 欠点は、暗号化セキュリティが低いことです。埋め込まれたメッセージは、追加の暗号化が施されていない場合、テキスト エディタを使用して読み取ることができます。

実用部

プログラム設計

暗号化および復号化アルゴリズムの実装に必要なツールはすべてパッケージに含まれます。 com.tsarik.steganography。 このパッケージにはインターフェースが含まれています Encryptor メソッド付き encrypt и decrypt、 クラス Binary、ビット配列および例外クラスを操作する機能を提供します。 UnableToEncryptException и UnableToDecryptException、インターフェースメソッドで使用する必要があります Encryptor エンコードエラーとデコードエラーがそれぞれ発生した場合。

メインプログラムパッケージ com.tsarik.programs.gifed 静的メソッドを備えた実行可能なプログラム クラスが含まれます main、プログラムを実行できるようになります。 プログラムパラメータを格納するクラス。 および他のクラスとのパッケージ。

アルゴリズム自体の実装はパッケージ内で提供されます。 com.tsarik.programs.gifed.gif クラス GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod。 これらのクラスはどちらもインターフェイスを実装します。 Encryptor.

GIF 形式の構造に基づいて、画像パレットにメッセージを導入するための一般的なアルゴリズムを作成できます。

GIF のステガノグラフィー

画像内にメッセージが存在するかどうかを判断するには、メッセージの先頭に特定のビット シーケンスを追加する必要があります。デコーダは最初にそれを読み取って、その正しさをチェックします。 一致しない場合、画像には隠されたメッセージがないと考えられます。 次に、メッセージの長さを指定する必要があります。 次に、メッセージ自体のテキスト。

アプリケーション全体のクラス図:

GIF のステガノグラフィー

プログラムの実施

プログラム全体の実装は、インターフェイスの暗号化メソッドと復号化メソッドの実装という XNUMX つのコンポーネントに分けることができます。 Encryptor、授業中 GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod、ユーザーインターフェイスの実装。

クラスを考慮してください GIFEncryptorByLSBMethod.

GIF のステガノグラフィー

フィールド firstLSBit и secondLSBit メッセージを入力するイメージの各バイトのビット数と、メッセージを読み取る場所のビット数が含まれます。 分野 checkSequence 埋め込まれたメッセージの認識を確実にするために、チェック ビット シーケンスを保存します。 静的メソッド getEncryptingFileParameters 指定されたファイルのパラメータと潜在的なメッセージの特性を返します。

メソッドアルゴリズム encrypt クラス GIFEncryptorByLSBMethod:

GIF のステガノグラフィー

そして彼のコード:

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

メソッドのアルゴリズムとソースコード decrypt クラス GIFEncryptorByLSBMethod:

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

クラスの実装 GIFEncryptorByPaletteExtensionMethod 情報の保存・読み込み方法が異なるだけで同様となります。

クラスで MainFrame ラッパーメソッドについては次のように説明されています。 encryptImage(Encryptor encryptor) и decryptImage(Encryptor encryptor)、インターフェースメソッドの結果を処理する Encryptor ユーザーとの対話、つまりファイル選択ダイアログを開いたり、エラー メッセージを表示したりする。 他の方法と同様に: openImage()、ユーザーが画像を選択できるようにします。 exit()、アプリケーションを終了します。 これらのメソッドはから呼び出されます Actionの対応するメニュー項目。 このクラスはさらに補助メソッドを実装します。 createComponents() - フォームコンポーネントの作成、 loadImageFile(File f) — ファイルから特別なコンポーネントに画像をロードします。 クラスの実装 GIFEncryptorByPaletteExtensionMethod クラスの実装に似ています GIFEncryptorByLSBMethod、主な違いは、メッセージ バイトがパレットに書き込まれ、パレットから読み取られる方法にあります。

プログラム操作

LBS法

次のような画像があるとします。

GIF のステガノグラフィー

この画像では、パレットは 256 色で構成されています (ペイントの保存時)。 最初の XNUMX 色は、白、黒、赤、緑です。 他の色は黒です。 グローバル パレットのビット シーケンスは次のようになります。

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

GIF のステガノグラフィー

メッセージが埋め込まれると、下線付きのビットがメッセージのビットに置き換えられます。 結果として得られる画像は、元の画像とほとんど変わりません。

オリジナル
メッセージが埋め込まれた画像

GIF のステガノグラフィー
GIF のステガノグラフィー

パレット展開方法

この方法を使用してメッセージを含む画像を開くと、次の画像が表示されます。

GIF のステガノグラフィー

この方法が本格的なスパイ活動には機能しないことは明らかであり、メッセージの追加の暗号化が必要になる可能性があります。

アニメーション画像の暗号化/復号化は通常の静止画像と同様に機能しますが、アニメーションは壊れません。

使用したソース:

ダウンロード:

出所: habr.com

コメントを追加します