介紹
問候。
不久前,我在大學讀書時,有一門學科的課程作業「資訊安全的軟體方法」。 這份作業要求我們建立一個在 GIF 檔案中嵌入訊息的程式。 我決定用 Java 來做。
在這篇文章中,我將描述一些理論要點,以及這個小程式是如何創建的。
理論部分
GIF格式
GIF(圖形交換格式 - 一種用於交換影像的格式)是一種儲存圖形影像的格式,能夠以最多 256 色的格式儲存壓縮資料而不會降低品質。 此格式由 CompuServe 於 1987 年開發(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(最低有效位元)方法
- 調色板添加方法
最低有效位法 - 一種常用的隱寫術方法。 它包括用隱藏訊息的位元替換容器中的最後一個有效位元(在我們的例子中是全域調色板位元組)。
作為此方法的一部分,程式將使用全域調色板位元組中的最後兩位。 這意味著,對於 24 位元影像,調色板顏色為紅、藍、綠三個字節,在將訊息嵌入其中後,每個顏色分量最多將改變 3/255 漸變。 這種變化,首先,人眼是不可見或難以注意到的,其次,在低品質的資訊輸出設備上是不可見的。
資訊量將直接取決於影像調色板的大小。 由於調色板的最大尺寸為 256 種顏色,如果將兩個訊息位元寫入每種顏色的組件中,則最大訊息長度(圖像中最大調色板)為 192 位元組。 一旦訊息嵌入圖像中,檔案大小就不會改變。
調色板擴展方法,僅適用於 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 格式的結構,您可以建立一個通用演算法來將訊息引入圖像調色板:

為了確定影像中是否存在訊息,需要在訊息的開頭添加特定的位元序列,解碼器首先讀取該位元序列並檢查其正確性。 如果不匹配,則認為圖像中不存在隱藏訊息。 接下來您需要指定訊息的長度。 然後是訊息本身的文字。
整個應用的類別圖:

計劃的實施
整個程式的實作可以分為兩個部分:介面加密方法的實作和解密方法的實現 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,主要區別在於從調色板寫入和讀取訊息位元組的方式。
程序運行
位置服務法
假設有這樣的圖像:

在此圖像中,調色板由 256 種顏色組成(Paint 保存)。 前四種顏色分別是:白、黑、紅、綠。 其他顏色為黑色。 全域調色板位元序列如下:
11111111 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000...

嵌入訊息後,帶下劃線的位元將替換為訊息中的位元。 生成的圖像與原始圖像幾乎沒有什麼不同。
原
帶有嵌入訊息的圖像

![]()
調色板擴展方法
當您使用此方法開啟包含訊息的圖像時,您將看到以下圖片:

顯然,這種方法不適用於成熟的間諜活動,並且可能需要對訊息進行額外的加密。
動畫影像中的加密/解密運作方式與常規靜態影像中的加密/解密相同,但動畫不會被破壞。
使用的來源:
下載:
來源: www.habr.com
