restful api实现信息隐藏术(txt to bmp)

1:原理解析:(萌新第一次发博文,有点激动,如有错误,感谢指出)

什么位图

位图图像(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文件,确认了以上两点

--------------------------------------------------------------------------------------------------------------

2:实现

1实现思路:

上传隐藏

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

2.解码

1)正则查找image文件new.*.bmp

2)将读取BMP文件得到byte数组和原TXT文本长度传入解码函数

3)将解码后的byte数组转码传回前台

3流程

 

4代码实现

项目结构

--------------------------------------------------------------------------------------------------------------

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>