什么位图
位图图像(bitmap), 亦称为点阵图像或绘制图像,是由称作像素(图片元素)的单个点组成的。这些点可以进行不同的排列和染色以构成图样。
位图(map)局部放大后就可以看出如同十字绣一样,是对画布的不同坐标(x,y)上的像素赋予不同的颜色,从而在画布整体上构成完整的色彩缤纷的图像。
十字绣是典型的位图,如下图所示。
色彩深度
色彩深度又叫色彩位数,即位图中要用多少个二进制位来表示每个点的颜色,是分辨率的一个重要指标。常用有1位(单色),2位(4色,CGA),4位(16色,VGA),8位(256色),16位(增强色),24位(真彩色)和32位等。色深16位以上的位图还可以根据其中分别表示RGB三原色或CMYK四原色(有的还包括Alpha通道)的位数进一步分类,如16位位图图片还可分为R5G6B5,R5G5B5X1(有1位不携带信息),R5G5B5A1,R4G4B4A4等等。
24位RGB色彩空间
RGB:位图颜色的一种编码方法,用红、绿、蓝三原色的光学强度来表示一种颜色。这是最常见的位图编码方法,可以直接用于屏幕显示。
24位RGB色彩编码中,红、绿、蓝三原色的亮度均可从暗到亮分划分为256个梯度。
每个色彩的变化范围正好可以用一个字节表示,该字节的ASC变化范围为0-255,表示256个色彩梯度。
24位的意思即red(8位)+ green(8位)+ blue(8位) = 24 位。
例子:如果一个点的颜色编码为a75ea8则表示:
R分量梯度为167
G分量梯度为94
B分量梯度为a8
其形成的该点的颜色为:
全黑的点RGB值为0x000000,全白的点RGB值为0xFFFFFF
--------------------------------------------------------------------------------------------------------------
我们需要做的是将txt文件的每个bit按顺序分别替换掉上述每个颜色码r,g,b的8bit的最后一位,根据上述的色阶图可知,当我们只修改最后一个bit时颜色值与原文件只相差|1|,肉眼几乎分辨不出来
查找百度百科可知BMP文件数据结构如下
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;//本结构所占用字节数(15-18字节)
LONG biWidth;//位图的宽度,以像素为单位(19-22字节)
LONG biHeight;//位图的高度,以像素为单位(23-26字节)
WORD biPlanes;//目标设备的级别,必须为1(27-28字节)
WORD biBitCount;//每个像素所需的位数,必须是1(双色),(29-30字节)
//4(16色),8(256色)16(高彩色)或24(真彩色)之一
DWORD biCompression;//位图压缩类型,必须是0(不压缩),(31-34字节)
//1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
DWORD biSizeImage;//位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位(35-38字节)
LONG biXPelsPerMeter;//位图水平分辨率,每米像素数(39-42字节)
LONG biYPelsPerMeter;//位图垂直分辨率,每米像素数(43-46字节)
DWORD biClrUsed;//位图实际使用的颜色表中的颜色数(47-50字节)
DWORD biClrImportant;//位图显示过程中重要的颜色数(51-54字节)
}__attribute__((packed)) BITMAPINFOHEADER;
可以看出BMP文件实际存储颜色的部分偏移量为54个字节,而TXT文件格式则相对简单,无头部信息直接保存文字编码,后来我用WinHex查看TXT文件和BMP文件,确认了以上两点
--------------------------------------------------------------------------------------------------------------
上传隐藏
1)运用RestFul编写API用于处理前端页面的请求
(官方文档地址:https://jersey.github.io/documentation/latest/index.html)
2)从前台读取上传的文件,保存到TomCat下的相应工程中的 Image文件夹
3)用文件流将TXT文本和BMP转化为byte数组,使用Java工具包BitSet将TXT文件按位写入BMP中,然后输出隐藏后的byte数组重新写入bmp文件,命名格式new.(TXT文本长度).bmp
1)正则查找image文件new.*.bmp
2)将读取BMP文件得到byte数组和原TXT文本长度传入解码函数
3)将解码后的byte数组转码传回前台
项目结构
--------------------------------------------------------------------------------------------------------------
Api:
package restful.api;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
importrestful.binding.MyRequestBinding.MyRequestBindingFilter;
importrestful.binding.MyResponseBinding.MyResponseBindingFilter;
@Path("/a")
public class MyAPI {
@POST
@Path("/writeImage")
@MyRequestBindingFilter
publicString writeImage() {
returnnull;
}
@GET
@Path("readImage")
@MyResponseBindingFilter
publicString readImage() {
return null;
}
}
--------------------------------------------------------------------------------------------------------------
过滤器绑定
package restful.binding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.ws.rs.NameBinding;
public class MyRequestBinding {
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@NameBinding
[email protected] MyRequestBindingFilter{};
}
package restful.binding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.ws.rs.NameBinding;
public class MyResponseBinding {
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@NameBinding
[email protected] MyResponseBindingFilter{};
}
--------------------------------------------------------------------------------------------------------------
请求过滤器
package restful.filter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
importjava.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
importjavax.imageio.stream.FileImageOutputStream;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjavax.ws.rs.container.ContainerRequestContext;
importjavax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Context;
importorg.apache.commons.codec.CharEncoding;
import org.apache.commons.fileupload.DiskFileUpload;
importorg.apache.commons.fileupload.FileItem;
importorg.apache.commons.fileupload.FileUploadException;
importorg.apache.commons.fileupload.RequestContext;
import org.jboss.resteasy.util.DateUtil;
import restful.binding.MyRequestBinding.MyRequestBindingFilter;
import tools.TextHiddenToBMP;
import com.jnetdirect.a.i;
import com.sun.xml.fastinfoset.Decoder;
@MyRequestBindingFilter
public class SteganalysisRequestFilterimplements ContainerRequestFilter {
@Context
privateHttpServletRequest httpServletRequest;
@Context
privateHttpServletResponse httpServletResponse;
privateString path;
@Override
publicvoid filter(ContainerRequestContext requestContext) throws IOException {
System.out.println("----------ContainerRequestFilteris invoke!--------------");
//获取image文件夹路径
this.path= httpServletRequest.getRealPath("image");
//保存上传的文件
savaInFile(path);
//读取文件
byte[]txt = readFile(path,"信息A.txt");
byte[]image = readFile(path, "载体B.bmp");
//将文本隐藏到b'm'p中
TextHiddenToBMPtextHiddenToBMP=new TextHiddenToBMP();
byte[]newImage=textHiddenToBMP.writeToBMP(txt, image);
//将新生成的文件保存为image文件夹下的newImnage.bmp
Filefile = new File(path+"\\"+"new."+txt.length+".bmp");
if(!file.exists())file.createNewFile();
FileImageOutputStreamoutputStream=new FileImageOutputStream(file);
outputStream.write(newImage);
outputStream.close();
StringimagePath=null;
Patternpattern=Pattern.compile("new.*.bmp");
Matchermatcher=null;
file=newFile(path);
String[]list = file.list();
for(inti=0;i<list.length;i++){
matcher=pattern.matcher(list[i]);
if(matcher.matches()){
imagePath=list[i];
break;
};
}
String[]split = imagePath.split("/");
intlength = split.length;
if(length>1)imagePath=split[length];
elseimagePath=split[0];
Strings="<script>window.location.href ='../image/"+imagePath+"'</script>";
httpServletResponse.setContentType("text/html;charset=utf-8");
PrintWriterwriter = httpServletResponse.getWriter();
writer.print(s);
writer.flush();
writer.close();
}
publicvoid savaInFile(String path){
try{
DiskFileUploaddf = new DiskFileUpload();
df.setHeaderEncoding(this.httpServletRequest.getCharacterEncoding());
List<FileItem>listItem = df.parseRequest(this.httpServletRequest);
Iterator<FileItem>iterator = listItem.iterator();
Filefile=new File(path);
if(!file.exists())file.mkdir();
while(iterator.hasNext()){
FileItemnext = iterator.next();
StringfileName = new String(next.getName().getBytes(),"utf-8");
Stringurl=path+"\\"+fileName;
next.write(newFile(url));
}
}catch (Exception e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 输入文件吗,名和路径,返回文件额度字节数组
* @param url 路径
* @param fileName 文件全名
* @return
* @throws UnsupportedEncodingException
*/
publicbyte[] readFile(String url,String fileName){
Filefile=null;
FileInputStreamfileInputStream=null;
byte[]bt=null;
try{
file=newFile(url+"\\"+fileName);
fileInputStream=newFileInputStream(file);
bt= new byte[(int)file.length()];
fileInputStream.read(bt);
fileInputStream.close();
}catch (IOException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
returnbt;
}
}
--------------------------------------------------------------------------------------------------------------
响应过滤器
package restful.filter;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjavax.ws.rs.container.ContainerRequestContext;
importjavax.ws.rs.container.ContainerResponseContext;
importjavax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Context;
importorg.apache.commons.io.filefilter.RegexFileFilter;
importrestful.binding.MyResponseBinding.MyResponseBindingFilter;
import tools.TextHiddenToBMP;
@MyResponseBindingFilter
public class SteganalysisResponseFilterimplements ContainerResponseFilter {
@Context
privateHttpServletRequest httpServletRequest;
@Context
privateHttpServletResponse httpServletResponse;
finalPrintWriter writer=null;
@Override
publicvoid filter(ContainerRequestContext reqContext, ContainerResponseContextrespContext) throws IOException {
System.out.println("----------ContainerResponseFilteris invoke!--------------");
try{
//寻找编码后的文件并读出txt长度
StringimagePath=null;
Patternpattern=Pattern.compile("new.*.bmp");
Matchermatcher=null;
Filefile=new File(httpServletRequest.getRealPath("image"));
String[]list = file.list();
for(inti=0;i<list.length;i++){
matcher=pattern.matcher(list[i]);
if(matcher.matches()){
imagePath=list[i];
break;
};
}
StringfileName=imagePath;
String[]split = imagePath.split("/");
intlength = split.length;
if(length>1)imagePath=split[length];
elseimagePath=split[0];
split=imagePath.split("\\.");
//读取编码后的图片
Filefile1=null;
FileInputStreamfileInputStream=null;
byte[]bt=null;
file1=newFile(this.httpServletRequest.getRealPath("image")+"\\"+fileName);
fileInputStream=newFileInputStream(file1);
bt= new byte[(int)file1.length()];
fileInputStream.read(bt);
fileInputStream.close();
//解码
byte[]deCodeFromBMP = newTextHiddenToBMP().deCodeFromBMP(bt,Integer.parseInt(split[1]));
//向页面打印内容
Strings=new String(deCodeFromBMP,"GBK");
httpServletResponse.setContentType("text/html;charset=utf-8");
PrintWriterwriter = httpServletResponse.getWriter();
writer.print("<!doctypehtml>"
+"<head>"
+"<body>"
+"<pre>"
+s
+"</pre>"
+"</body>"
+"</head>");
writer.flush();
writer.close();
}catch(IOException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
}
--------------------------------------------------------------------------------------------------------------
TXT的隐藏和解码工具类
packagetools;
importjava.util.BitSet;
publicclass TextHiddenToBMP {
staticprivateint start=56*8;//实际颜色标识符的开始一个字节的最后一位
publicbyte[] writeToBMP(byte[] text,byte[] bmp){
BitSet txt = BitSet.valueOf(text);
BitSet image = BitSet.valueOf(bmp);
int bmplenth=image.length();
int txtlenth=txt.length();
System.out.println(txtlenth);
for(int i=start,j=0;i<bmplenth && j<txtlenth;i+=8,j++){
image.set(i, txt.get(j));
}
return image.toByteArray();
}
publicbyte[] deCodeFromBMP(byte[] bmp,int txtLenth){
BitSet image = BitSet.valueOf(bmp);
int bmplenth=image.length();
BitSet txt=new BitSet();
txtLenth=txtLenth*8;
System.out.println(txtLenth);
for(int i=start,j=0;i<bmplenth && j<txtLenth;i+=8,j++){
txt.set(j, image.get(i));
}
return txt.toByteArray();
}
}
--------------------------------------------------------------------------------------------------------------
前端
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="js/jquery-3.1.0.min.js"></script> <title></title> </head> <body> <form action="a/writeImage" method="post" enctype="multipart/form-data"> <div align="center"> <fieldset style="width:80%"> <legend>上传文件</legend><br/> <div align="left">需要保密的文件</div> <div align="left"> <input type="file" name="docField"/> </div> <div align="left">保密信息载体BMP图片文件</div> <div align="left"> <input type="file" name="imageField"/> </div> <div> <div align='left'> <input type='submit' value="上传文件"/> </div> </div> </fieldset> </div> </form> </body> </html>