GIFステガノグラフィ

はじめに



ご挨拶。

少し前まで、私が大学で勉強していたとき、「情報保護のソフトウェア手法」という分野で授業がありました。 割り当て時には、メッセージをGIFファイルに埋め込むプログラムを作成する必要がありました。 Javaで行うことにしました。



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







理論部



GIF形式


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



GIFファイルにはブロック構造があります。 これらのブロックは常に固定長である(またはいくつかのフラグに依存する)ため、どのブロックが配置されているかを間違えることはほとんど不可能です。 最も単純な非アニメーションGIF画像形式GIF89aの構造:







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



大きさ 色数 パレットサイズ、バイト
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法は、一般的なステガノグラフィ法です。 これは、コンテナの最後の重要なビット(この場合、グローバルパレットのバイト)を非表示にするメッセージのビットで置き換えることで構成されています。



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



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



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



実用部



プログラム設計


暗号化および復号化アルゴリズムの実装に必要なすべてのツールは、 com.tsarik.steganography



パッケージにcom.tsarik.steganography



ます。 このパッケージには、 encrypt



decrypt



メソッドとdecrypt



メソッドを備えたEncryptor



インターフェイス、ビット配列を操作する機能を提供するBinary



クラス、およびエンコードおよびデコードエラーの場合にそれぞれEncryptor



インターフェイスメソッドで使用されるUnableToEncryptException



およびUnableToDecryptException



例外クラスが含まれます。



メインプログラムパッケージcom.tsarik.programs.gifed



には、プログラムを実行できる静的main



メソッドで起動されるプログラムクラスが含まれます。 プログラムパラメータを格納するクラス。 他のクラスのパッケージ。



アルゴリズム自体の実装は、 GIFEncryptorByLSBMethod



およびGIFEncryptorByPaletteExtensionMethod



com.tsarik.programs.gifed.gif



パッケージcom.tsarik.programs.gifed.gif



提示されGIFEncryptorByPaletteExtensionMethod



。 これらのクラスは両方ともEncryptor



インターフェイスを実装します。



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







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



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







プログラムの実施


プログラム全体の実装は、 GIFEncryptorByLSBMethod



およびGIFEncryptorByPaletteExtensionMethod



Encryptor



インターフェースの暗号化および復号化メソッドの実装、およびユーザーインターフェースの実装の2つのコンポーネントに分けることができます。



GIFEncryptorByLSBMethod



クラスを検討してGIFEncryptorByLSBMethod











firstLSBit



およびsecondLSBit



は、メッセージの入力先および送信元の画像の各バイトのビット番号が含まれます。 checkSequence



フィールドは、埋め込まれたメッセージの認識を保証するためにビットのチェックシーケンスを保持します。 静的メソッドgetEncryptingFileParameters



は、指定されたファイルのパラメーターと潜在的なメッセージの特性を返します。



encrypt



クラスのencrypt



メソッドのアルゴリズム:







そしてそのコード:

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







GIFEncryptorByLSBMethod



クラスのdecrypt



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







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



記述しますencryptImage(Encryptor encryptor)



インターフェースメソッドの結果を処理し、ユーザーと対話する、つまりファイル選択ダイアログを開く、エラーメッセージを表示するなど、 decryptImage(Encryptor encryptor)



encryptImage(Encryptor encryptor)



およびdecryptImage(Encryptor encryptor)



です。 ; 他のメソッド: openImage()



、ユーザーが画像を選択できるようにする、 exit()



、アプリケーションを終了する これらのメソッドは、対応するメニュー項目のアクションから呼び出されます。 このクラスでは、補助メソッドが追加で実装されますcreateComponents()



-フォームコンポーネントの作成、 loadImageFile(File f)



-ファイルから特別なコンポーネントに画像をロードします。 GIFEncryptorByPaletteExtensionMethod



クラスの実装はGIFEncryptorByPaletteExtensionMethod



クラスの実装に似ていますが、主な違いは、メッセージバイトがパレットで読み書きされる方法にあります。



プログラム作業



LBS法


そのような画像があるとしましょう:







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



111111 11 111111 11 111111 11 000 000 00 000 000 00 000 000 00 111111 11 000 000 00 000 000 00 000 000 00 111111 11 000 000 00 ...







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



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




パレット拡張方法


このメソッドを使用してメッセージが配置されている画像を開くと、次の画像を見つけることができます。







このような方法は本格的なスパイ活動では機能せず、追加のメッセージ暗号化が必要になる場合があることは明らかです。



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



使用されたソース:





ダウンロード:




All Articles