介绍
欢迎光临!
不久前,我在大学读书时,有一门学科的课程作业“信息安全的软件方法”。 该作业要求我们创建一个在 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...

嵌入消息后,带下划线的位将替换为消息中的位。 生成的图像与原始图像几乎没有什么不同。
原
带有嵌入消息的图像

![]()
调色板扩展方法
当您使用此方法打开包含消息的图像时,您将看到以下图片:

显然,这种方法不适用于成熟的间谍活动,并且可能需要对消息进行额外的加密。
动画图像中的加密/解密工作方式与常规静态图像中的加密/解密相同,但动画不会被破坏。
使用的来源:
下载:
来源: habr.com
