導入
ようこそ。
少し前、私が大学で勉強していたとき、「情報を保護するためのソフトウェア手法」という科目の授業がありました。課題では、GIF ファイルにメッセージを埋め込むプログラムを作成する必要がありました。 Java で実行することにしました。
この記事では、いくつかの理論的なポイントと、この小さなプログラムがどのように作成されたかについて説明します。
理論部分
GIF形式
GIF (Graphics Interchange Format) は、グラフィック画像を保存するための形式で、最大 256 色の形式で品質を損なうことなく圧縮データを保存できます。この形式は、ネットワーク経由でラスター画像を送信するために、1987 年に CompuServe によって開発されました (GIF87a)。 1989 年に、形式が変更され (GIF89a)、透明度とアニメーションのサポートが追加されました。
GIF ファイルはブロック構造になっています。これらのブロックの長さは常に固定されている (またはいくつかのフラグに依存する) ため、どのブロックがどこに配置されているかを間違えることはほとんどありません。 GIF89a 形式の最も単純な非アニメーション 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 形式の構造に基づいて、画像パレットにメッセージを埋め込むための一般的なアルゴリズムを作成できます。

画像内にメッセージが存在するかどうかを判断するには、メッセージの先頭に特定のビットシーケンスを追加する必要があります。デコーダーは最初にこれを読み取り、正確性を確認します。一致しない場合は、画像に隠されたメッセージがないものとみなされます。次に、メッセージの長さを指定する必要があります。次に、メッセージ自体のテキストです。
アプリケーション全体のクラス図:

プログラムの実施
プログラム全体の実装は、インターフェースの暗号化と復号化の方法の実装という2つのコンポーネントに分けることができます。 Encryptor、授業で GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod、およびユーザー インターフェイスの実装。
クラスを考えてみましょう GIFEncryptorByLSBMethod.

フィールド firstLSBit и secondLSBit メッセージが書き込まれるイメージとメッセージが読み取られるイメージの各バイトのビット番号が含まれます。分野 checkSequence 埋め込まれたメッセージの認識を確実にするために制御ビット シーケンスを保存します。静的メソッド getEncryptingFileParameters 指定されたファイルのパラメータと潜在的なメッセージの特性を返します。
方法のアルゴリズム encrypt クラス GIFEncryptorByLSBMethod:

そしてそのコード:
@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:

@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法
次のような画像があるとします。

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

メッセージが埋め込まれた後、下線付きのビットはメッセージのビットに置き換えられます。結果として得られる画像は、元の画像とほとんど区別がつきません。
オリジナル
メッセージが埋め込まれた画像

![]()
パレット拡張法
この方法を使用してメッセージを含む画像を開くと、次の画像が表示されます。

この方法は本格的なスパイ活動には機能せず、メッセージの追加暗号化が必要になる可能性があることは明らかです。
アニメーション画像の暗号化/復号化は通常の静止画像と同じように機能し、アニメーションは壊れません。
使用したソース:
ダウンロード:
出所: habr.com
