Java实现在bmp图像文件中隐藏与导出信息
文章来源:【http://tech.sina.com.cn/s/2008-07-04/0923720265.shtml】
BMP图像文件,即所谓的位图文件。在位图中,其表示方式是将一幅图像分割成栅格,栅格的每一点称为像素,每一个像素具有自己的RGB值,以此构成图形。所以从本质上讲,一幅位图不过是由一系列像素点构成的点阵罢了。
位图文件支持4位RLE(行程长度编码)以及8位和24位编码。本人在此类中只处理了24位格式。
24位BMP图像文件的结构特点为:
(1)每个文件只能非压缩地存放一幅彩色图像;
(2)文件头由54个字节的数据段组成,其中包含有该位图文件的类型、大小、图像尺寸及打印格式等;
(3)从第55个字节开始,是该文件的图像数据部分,数据的排列顺序以图像的左下角为起点,从左到右、从下到上,每连续3个字节便描述图像一个像素点的颜色信息,这三个字节分别代表蓝、绿、红三基色在此像素中的亮度,若某连续三个字节为:00H,00H,FFH,则表示该像素的颜色为纯红色。
要将文件隐藏在图片中,有两种模型方法可供参考:
一是将文件附在载体图片之后,利用BMP文件的特殊性质(系统在读取BMP文件的时候,是读取它的第3~6个字节为文件长度,对超出这个长度的部分会忽略),将目标图片的二进制文件直接附着在载体之后,来实现文件的隐藏,此方法简单易行,不会破坏载体与目标图片的任何信息,且对隐藏文件的大小没有限制,但隐蔽性有待加强。
二是采用LSB算法,将所得目标文件化整为零分别隐藏在载体的位图信息的每个字节的最低位,使得文件的隐蔽性大大提高,但可能因为载体的容量有限导致目标文件无法装入且对隐藏文件的长度且有严格限制,此时一是用无损压缩的办法使隐藏文件变小,二是放大载体图像,三是增加在载体文件每字节存放的位数。
但这两种模型的优缺点在一定程度上可以互补,对于实际问题,可根据特定情况将以上两种想法结合起来,得到更理想的模型,即当载体图片大小满足做法二的要求时,则按位加入,否则将剩余的二进制位按照第一种做法直接加在载体图片后。
(1)模型1 ——尾部附加法。通过对BMP图像文件的数据结构的分析在BMP图像的头文件中有指示文件大小的数值bfSIZE。它指定了一般的图片浏览器所能读取的范围。若不改变该数值的大小,则一般的图片浏览器只能够读取原文件。即便在该文件的尾部接上其他的文件,图片浏览器所显示的仍然只是原文件。这就相当于把后续文件给屏蔽掉了,可以达到隐藏信息的目的。一旦图片被截获者截获,一般的图片浏览器对图片的读取也只能进行到载体部分,目标图片不会被暴露出来,达到隐藏的效果。
(2)模型2 ——内部嵌入法。对第一种模型的不足,即截获者可以用一些特殊方式发觉载体图片隐藏着一些信息,那么隐蔽性就大打折扣。通过研究发现,对一幅用多比特值表示其灰度的图像来说,其中每个比特可看作表示了一个二值平面,也称作“位面”。“1幅灰度级用8bit表示的图像有8个位面,一般用0代表最低位面,位面7代表最高位面。基本上5个最高面含有视觉可见的有意义的信息,在其余的位面中几乎没有任何视觉信息,这些位面所显示的只是图像中很细小的局部,在很多情况下,它们可看作噪声”正因为图像具有位面这种性质,因此信息往往隐藏在不为人视觉所察觉的位置,这样位面就为信息隐藏提供了一种很好的实施方案。将信息隐藏在这些看似噪声的位置,其对图像的破坏就不会太大,当然,在嵌入信息之前,首先要选择好信息具体加入的位面位置,不能将信息加入在存在图像视觉信息的位面上,因此,一般的LSB算法中,信息一般加在图像的后4位。
(1)嵌入秘密信息的具体步骤:
第一步:读入载体文件,并显示它;
第二步:决定载体的LSB及嵌入的位数,本文采用嵌入图像中所有象素的最后一位,即第8位;
第三步:对载体图像做预处理,置其LSB为0;
第四步:将秘密信息以ACILL码的形式读入,并存储;
第五步:在每一个象素的第LSB位上,存储秘密信息的一个bit;
第六步:显示嵌入秘密文件的图像;
(2)读取秘密信息的具体步骤:
第一步:读入含有秘密文件的图像;
第二步:得到每一个象素点的LSB位;
第三步:由每8个LSB位组成一个ASILL还原秘密信息。
实现起来其实很简单,无论使用哪种理论模型,大体都是利用数据偏移罢了,偶用的内部嵌入法,大家可以根据自己的需要变更偶的代码,也欢迎大家创造的隐藏方法,那位大侠有时间更改的话给偶也寄一份,我的Email:ceponline@yahoo.com.cn。
本类从功能上讲,可以把任意文件(不能超过bmp文件大小)隐藏到BMP图片中,图片大小基本不变,浏览图片也看不出变化。当然,也可以把隐藏在图片中的文件提取出来。
代码如下,由3个类构成:
BitmapInput.java
package org.loon.framework.test.encode;
import java.awt.image.BufferedImage;
/** *//**
* * Title: LoonFramework
* Description:
*
* Copyright: Copyright (c) 2007
*
* Company: LoonFramework
*
* @author chenpeng
* @email:ceponline@yahoo.com.cn
* @version 0.1
*/
public class BitmapInput {
private BufferedImage _bmp;
private int curX, curY, iRGB;
private int bitsLeft;
public BitmapInput(BufferedImage bmp) {
curX = curY = iRGB = 0;
this._bmp = bmp;
bitsLeft = bmp.getHeight() * bmp.getWidth() * 3;
}
public BufferedImage getBufferedImage() {
return _bmp;
}
public synchronized Object[] readByte(int body) {
body = 0;
if (bitsLeft < {
return new Object[] { "false", "0" };
}
int bit = 0;
int bits2Do = 8;
for (; curX < _bmp.getWidth(); curX++) {
if (curY >= _bmp.getHeight())
curY = 0;
for (; curY < _bmp.getHeight(); curY++) {
if (bits2Do == 0) {
return new Object[] { "true", String.valueOf(body) };
}
int rgb = _bmp.getRGB(curX, curY);
int r = (rgb & 0x00ff0000) >> 16;
int g = (rgb & 0x0000ff00) >> 8;
int b = (rgb & 0x000000ff);
while(true) {
switch (iRGB) {
case 0:
bit = (r & 1);
break;
case 1:
bit = (g & 1);
break;
case 2:
bit = (b & 1);
break;
}
--bits2Do;
--bitsLeft;
body |= (int) (bit << 7);
if (bits2Do != 0) {
body >>= 1;
}
if (iRGB == 2) {
iRGB = 0;
break;
}
iRGB++;
if (bits2Do == 0) {
return new Object[] { "true", String.valueOf(body) };
}
}
}
}
return new Object[] { "true", String.valueOf(body) };
}
}
BitmapOutput.java
package org.loon.framework.test.encode;
import java.awt.image.BufferedImage;
/** *//**
* Title: LoonFramework
*
* Description:
*
* Copyright: Copyright (c) 2007
*
* Company: LoonFramework
* @author chenpeng
* @email:ceponline@yahoo.com.cn
* @version 0.1
*/
public class BitmapOutput {
private BufferedImage _bmp;
private int curX, curY, iRGB;
private int bitsLeft;
private int r, g, b;
public BitmapOutput(BufferedImage bmp) {
this._bmp = bmp;
curX = curY = iRGB = 0;
bitsLeft = (bmp.getHeight() * bmp.getWidth() * 3);
}
public BufferedImage getBufferedImage() {
return _bmp;
}
public synchronized boolean writeByte(int body) {
if (bitsLeft <
return false;
int bits2Do = 8;
for (; curX < _bmp.getWidth(); curX++) {
if (curY >= _bmp.getHeight()) {
curY = 0;
}
for (; curY < _bmp.getHeight(); curY++) {
if (bits2Do == 0)
return true;
int rgb = _bmp.getRGB(curX, curY);
//转化为r,g,b格式
r = (rgb & 0x00ff0000) >> 16;
g = (rgb & 0x0000ff00) >> 8;
b = (rgb & 0x000000ff);
while (true) {
int curBit = (body & 1);
switch (iRGB) {
case 0:
r = (r & 0xFE);
r |= curBit;
break;
case 1:
g = (g & 0xFE);
g |= curBit;
break;
case 2:
b = (b & 0xFE);
b |= curBit;
break;
}
--bits2Do;
--bitsLeft;
body >>= 1;
//还原
rgb = (r << 16) | (g << | b;
//重新注入
_bmp.setRGB(curX, curY, rgb);
if (iRGB == 2) {
iRGB = 0;
break;
}
iRGB++;
if (bits2Do == 0)
return true;
}
}
}
return true;
}
}
BitmapExecute.java
package org.loon.framework.test.encode;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
/** *//**
*
* Title: LoonFramework
*
* Description: 利用bmp文件进行数据的隐藏与导出
*
* Copyright: Copyright (c) 2007
*
* Company: LoonFramework
*
*
* @author chenpeng
* @email:ceponline@yahoo.com.cn
* @version 0.1
*/
public class BitmapExecute {
/** *//**
* 将BufferedImage转化为bmp文件保存在指定位置
*
* @param image
* @param file
* @return
*/
private static boolean saveBMP(BufferedImage image, File file) {
// 格式化为bmp文件
Iterator writers = ImageIO.getImageWritersByFormatName("bmp");
ImageWriter writer = (ImageWriter) writers.next();
ImageOutputStream ios = null;
try {
ios = ImageIO.createImageOutputStream(new FileOutputStream(file));
} catch (IOException ioe) {
return false;
}
writer.setOutput(ios);
try {
writer.write(image);
} catch (IOException ioe) {
return false;
}
return true;
}
/** *//**
* 将数据文件隐藏入bmp文件中
*
* @param dataFileName
* @param bmpFileName
* @param outFileName
* @return
* @throws IOException
*/
public static boolean DataSourceToBMP(String dataFileName,
String bmpFileName, String outFileName) throws IOException {
return DataSourceToBMP(new File(dataFileName), new File(bmpFileName),
outFileName);
}
/** *//**
* 将数据文件隐藏入bmp文件中
*
* @param dataFileName
* @param bmpFileName
* @param outFileName
* @return
* @throws IOException
*/
public static boolean DataSourceToBMP(File dataFile, File bmpFile,
String outFileName) throws IOException {
FileInputStream dataStream = new FileInputStream(dataFile);
BufferedImage bmp;
try {
bmp = ImageIO.read(bmpFile);
} catch (Exception ex) {
return false;
}
if (dataStream.available() == 0) {
return false;
}
int maxByteStorage = (bmp.getHeight() * bmp.getWidth() * 3) / 8;
// bmp文件必须较要隐藏的文件为大,否则无法注入文件
if (maxByteStorage < dataStream.available() + 500) {
return false;
}
BitmapOutput bmpWriter = new BitmapOutput(bmp);
int dataSize = dataStream.available();
try {
for (int u = 0; u < 500; u++) {
bmpWriter.writeByte(dataSize);
}
// 标记出完整数据
bmpWriter.writeByte(91);
for (int u = 0; u < dataSize; u++) {
int result = dataStream.read();
if (result == 91) {
bmpWriter.writeByte(123);
} else if (result == 93) {
bmpWriter.writeByte(125);
} else {
bmpWriter.writeByte(result);
}
}
bmpWriter.writeByte(93);
} catch (Exception ex) {
ex.getStackTrace();
return false;
}
try {
File file = new File(outFileName);
if (file.exists()) {
file.delete();
}
// 保存BufferedImage为bmp文件
saveBMP(bmpWriter.getBufferedImage(), new File(outFileName));
} catch (Exception ex) {
ex.getStackTrace();
return false;
}
return true;
}
/** *//**
* 从bmp文件中导出隐藏数据(由于隐藏数据的方式不同,只对此类隐藏的有效)
*
* @param bmpFileName
* @param outFName
* @return
* @throws IOException
*/
public static boolean BMPToDataSource(String bmpFileName, String outFName)
throws IOException {
return BMPToDataSource(new File(bmpFileName), outFName);
}
/** *//**
* 从bmp文件中导出隐藏数据(由于隐藏数据的方式不同,只对此类隐藏的有效)
*
* @param bmpFile
* @param outFName
* @return
* @throws IOException
*/
public static boolean BMPToDataSource(File bmpFile, String outFName)
throws IOException {
BufferedImage image = ImageIO.read(bmpFile);
BitmapInput bmpReader;
try {
bmpReader = new BitmapInput(image);
} catch (Exception ex) {
return false;
}
FileOutputStream outStream;
try {
File file = new File(outFName);
if (!file.exists()) {
file.createNewFile();
}
outStream = new FileOutputStream(file);
} catch (Exception ex) {
return false;
}
int dataSize = 0;
int outByte = 0;
int count = 0;
try {
for (int u = 0; u < 500; u++) {
// 以对象数组返回body和验证布尔值
Object[] object = bmpReader.readByte(outByte);
boolean header = Boolean.parseBoolean((String) object[0]);
outByte = Integer.parseInt((String) object[1]);
if (!header) {
throw new Exception();
}
dataSize |= (int) (outByte << 8 * 3);
if (u != 3) {
dataSize >>= 8;
}
}
for (int u = 0; u < dataSize; u++) {
Object[] object = bmpReader.readByte(outByte);
boolean header = Boolean.parseBoolean((String) object[0]);
outByte = Integer.parseInt((String) object[1]);
if (!header) {
throw new Exception();
}
if (outByte == 93) {
return true;
}
if (outByte == 91) {
count += 1;
}
if (count > 0) {
if (outByte == 123) {
outStream.write(91);
} else if (outByte != 91) {
outStream.write(outByte);
}
}
}
} catch (Exception ex) {
return false;
} finally {
try {
outStream.flush();
outStream.close();
outStream = null;
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
}
测试用类,BMPHiedData.java:
package org.loon.framework.test.encode;
import java.io.IOException;
/** *//**
*Title: LoonFramework
*Description:利用bmp文件隐藏数据
*Copyright: Copyright (c) 2007
*Company: LoonFramework
* @author chenpeng
* @email:ceponline@yahoo.com.cn
* @version 0.1
*/
public class BMPHiedData {
public static void main(String[]args){
//导出为bmp
try {
//参数分别为:
//1.要隐藏的数据
//2.隐藏数据用图
//3.导出位置
BitmapExecute.DataSourceToBMP( "c:/temp/txt.txt","c:/temp/12193.BMP", "c:/temp/temp.bmp");
} catch (IOException e) {
e.printStackTrace();
}
//导出bmp中隐藏的数据
try {
//参数分别为:
//1.隐藏数据用图
//2.导出数据位置
BitmapExecute.BMPToDataSource("c:/temp/temp.bmp", "c:/temp/txt_temp.txt");
} catch (IOException e) {
e.printStackTrace();
}
}
}