一、项目背景 随着Internet技术和多媒体信息技术的飞速发展,多媒体、计算机网络、个人移动通信技术等进入寻常百姓家,数字化已深入人心。数字多媒体信息在网上传播与传输越来越方便,通过网络传递各种信息越来越普遍。但与此同时也带来了信息安全的隐患问题。信息隐藏是近年来信息安全和多媒体信号处理领域中提出的一种解决媒体信息安全的新方法。它通过把秘密信息隐藏在可公开的媒体信息里,达到证实该媒体信息的数据完整性或传递秘密信息的目的,从而为数字信息的安全问题提供了一种新的解决方法。 二、软件原理及内容 1、原理 LSB(LeastSignificantBits)算法:将秘密信息嵌入到载体图像像素值的最低有效位,也称最不显著位,改变这一位置对载体图像的品质影响最小。 LSB算法基本原理:对空域的LSB做替换,用来替换LSB的序列就是需要加入的水印信息、水印的数字摘要或者由水印生成的伪随机序列。由于水印信息嵌入的位置是LSB,为了满足水印的不可见性,允许嵌入的水印强度不可能太高。然而针对空域的各种处理,如游程编码前的预处理,会对不显著分量进行一定的压缩,所以LSB算法对这些操作很敏感。因此LSB算法最初是用于脆弱性水印的。 2、内容 (1)编写程序,能够将至少20个汉字隐藏到一幅24位的bmp格式的图片中。 (2)用RSA算法将需要隐藏的信息进行加密,然后再隐藏。 (3)能使用密钥将隐藏信息解密提取出来。 三、实现步骤 1、找一幅bmp的24位图片。 2、通过网络学习RSA算法和LSB算法的相关原理和操作。 3、理清程序的运行流程设计好页面。 4、在NetBeansIDE编写代码实现相关功能。 5、调试程序并进行优化。 四、软件功能及分析 1、工程结构图。
图1 工程结构 分析说明: 图1中PrivateKey为私钥封装类,Publickey为公钥封装类,RSAGeneratorKey为生成密钥对的类,RSAUtil为含有加密和解密方法的封装类,RsaKeyPair为密钥对封装类,_LSB_BMP为包含信息加密解密等方法的主类。 2、程序运行初始界面。 图2 初始界面 分析说明: 图2显示的界面由标题栏、菜单栏、图片显示区和文本输入显示区构成。其中菜单栏中有文件、LSB和加密隐藏三个选项,“文件”选项主要用于打开已有的未加密图片和加密图片及保存隐藏信息后的图片,打开和保存的图片均为bmp格式;“LSB”主要用于图片的非加密隐藏和直接显示隐藏信息;“加密隐藏”选项主要用于将所需的隐藏信息进行RSA加密及将加密后的信息通过私钥进行解密。 3、点击菜单栏中“文件”的“打开”选项,可选择未加密信息的图片或者含隐藏信息的图片;若点击“保存”选项,则可以将含隐藏信息的图片进行保存。 图3 选择图片路径 图4 打开未隐藏信息的图片 图5 打开隐藏信息的图片 图6 保存隐藏信息的图片 分析说明: 图3中显示的界面为选择未隐藏信息的图片或者隐藏了信息的图片,图4为打开未隐藏信息的图片后所出现的界面,且在文本输入显示区中显示了可以嵌入多少字节的信息到图片中;图5为打开含隐藏信息的图片后的界面,可以看出含隐藏信息的图片和原图相比,凭肉眼是看不出区别的,但通过程序检测却能发现其中已经隐藏了一定的信息;图6为将含隐藏的图片进行保存,若保存成功会在文本输入显示区显示“保存成功!”。 4、点击菜单栏中的“加密隐藏”中的“隐藏”及“显示”。 图7 使用RSA对隐藏信息加密及保存私钥 图8 使用RSA私钥对隐藏信息解密 分析说明: 图7中表示使用RSA算法将需隐藏的信息加密后,把RSA的私钥通过文本保存的界面;图8显示的是通过选择图7保存的密钥解密后,显示的隐藏的信息。
源程序:
- /*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
- package bmp;
- /**
- * @author 13673
- */
- import java.awt.BorderLayout;
- import java.awt.Color;
- import java.awt.Dimension;
- import java.awt.Graphics;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.BufferedInputStream;
- import java.io.BufferedOutputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.UnsupportedEncodingException;
- import java.math.BigInteger;
- import java.util.Enumeration;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import javax.swing.JFileChooser;
- import javax.swing.JFrame;
- import javax.swing.JMenu;
- import javax.swing.JMenuBar;
- import javax.swing.JMenuItem;
- import javax.swing.JPanel;
- import javax.swing.JScrollPane;
- import javax.swing.JTextArea;
- import javax.swing.UIManager;
- import javax.swing.filechooser.FileNameExtensionFilter;
- import javax.swing.plaf.FontUIResource;
- /**
- *
- * @ClassName: 信息隐藏_LSB技术_于BMP位图
- * @Description: 基于24位BMP图片,运用LSB技术对文本信息进行隐藏
- * @author:
- * @date: 2019年5月3日
- */
- public class _LSB_BMP extends JFrame{
- private static final long serialVersionUID = 1L;
- RsaKeyPair keyPair=RSAGeneratorKey.generatorKey(1024);//产生Rsa密钥对
- boolean open_flag=false; //是否打开图片
- int map[][];//保存像素颜色的数组 int 4个字节
- int length_byte_sum=0;
- MyPanel center;//绘图面板
- File selectFile;//读取的文件
- int width;//图像宽度
- int height;//图像高度
- byte temp1[];//图像前18个字节的信息
- byte temp2[];//图像的28个字节的信息
- JScrollPane scrollpane;//滑动面板
- JTextArea infoJt;//文本信息区
- JMenuItem open; //打开文件
- JMenuItem save;
- JMenuItem hide;
- JMenuItem show;
- JMenuItem hide_rsa;
- JMenuItem show_rsa;
- int disWidth=1000;
- int disHeight=800;
- public _LSB_BMP() {
- setUIFont(new FontUIResource("微软雅黑", 0, 20)); //设置字体
- this.setLayout(new BorderLayout());//设置布局
- center=new MyPanel(); //初始化画图面板
- center.setBackground(Color.WHITE);
- center.setBackground(Color.GRAY);
- // center.setPreferredSize(new Dimension(200, 300));
- scrollpane=new JScrollPane(center);//用center初始化滚动面板
- // scrollpane.setPreferredSize(new Dimension(200,100));
- infoJt=new JTextArea(); //隐藏信息输入的控件
- infoJt.setLineWrap(true);
- infoJt.setWrapStyleWord(true);
- JScrollPane infoJs=new JScrollPane(infoJt);
- infoJs.setPreferredSize(new Dimension(disWidth, 300));
- MyListener lis=new MyListener(); //设置经停事件
- JMenuBar menuBar=new JMenuBar();
- JMenu fileMenu=new JMenu("文件");
- open=new JMenuItem("打开");
- save=new JMenuItem("保存");
- JMenu LSBMenu=new JMenu("LSB");
- hide=new JMenuItem("隐藏");
- show=new JMenuItem("显示");
- JMenu RSAMenu=new JMenu("加密隐藏");
- hide_rsa=new JMenuItem("隐藏");
- show_rsa=new JMenuItem("显示");
- open.addActionListener(lis);
- save.addActionListener(lis);
- hide.addActionListener(lis);
- show.addActionListener(lis);
- hide_rsa.addActionListener(lis);
- show_rsa.addActionListener(lis);
- fileMenu.add(open);
- fileMenu.add(save);
- menuBar.add(fileMenu);
- LSBMenu.add(hide);
- LSBMenu.add(show);
- menuBar.add(LSBMenu);
- RSAMenu.add(hide_rsa);
- RSAMenu.add(show_rsa);
- menuBar.add(RSAMenu);//添加到菜单栏
- this.setJMenuBar(menuBar);
- this.add(scrollpane,BorderLayout.CENTER);//加入滚动面板
- this.add(infoJs,BorderLayout.SOUTH);
- this.setTitle("LSB隐藏器");
- this.setSize(disWidth, disHeight);
- this.setLocationRelativeTo(null);//设置窗体出现在屏幕中间
- this.setDefaultCloseOperation(3);
- this.setVisible(true);
- }
- /**
- * 读取BMP文件
- */
- public void readBMP()
- {
- try {
- FileInputStream fis=new FileInputStream(selectFile); //选择文件
- //读取了一次就会依次往后移
- try (BufferedInputStream bis = new BufferedInputStream(fis) //输入的缓冲区
- ) {
- //读取了一次就会依次往后移
- byte[] wb=new byte[4];//存放宽度的字节数组
- byte[] hb=new byte[4];//存放高度的字节数组
- temp1=new byte[18]; //存放bmp前18个字节相关信息
- bis.read(temp1);//bis.skip(18);//跳过前18个byte
- String tp;
- //bis.skip(18);//跳过18个字节
- bis.read(wb);//读取宽度
- bis.read(hb);//读取高度
- width=byteToint(wb);
- System.out.println("wb:\""+width+"\"");
- height=byteToint(hb);
- System.out.println("hb:\""+height+"\"");
- map=new int[height][width];//保存图像的像素点的数组
- int skip=4-width*3%4;//得到每行要跳过的数字(与windows 系统机制有关)
- //使用调色板,然后前三个字节是颜色分量,最后个为空所以跳过
- temp2=new byte[28]; //bis.skip(28);
- bis.read(temp2);//bis.skip(28);
- if(temp1[10]==55)//偏移量为55,此位图已经隐入了信息数据
- bis.read();//跳过1字节
- for(int i=height-1;i>0;i--)
- {
- for(int j=0;j<width;j++)
- {
- int blue=bis.read(); //读取颜色的分量
- int green=bis.read();
- int red=bis.read();//
- Color c=new Color(red,green,blue);
- map[i][j]=c.getRGB(); //将RGB分量存入数组中
- }
- if(skip!=4)
- bis.skip(skip);
-
- }
- } //存放宽度的字节数组
- center.setPreferredSize(new Dimension(width,height));
- javax.swing.SwingUtilities.updateComponentTreeUI(center);//这里必须要用updateComponentTreeUI(center)函数
- //不能用repaint()函数
- } catch (IOException e) {
- }
- }
- //保存bmp
- public void writeBMP()
- {
- try {
- try (FileOutputStream fos = new FileOutputStream(selectFile)) {
- BufferedOutputStream bos=new BufferedOutputStream(fos);
- bos.write(temp1);// 心如头部文件
- bos.write(intTobyte(width,4));//宽度
- bos.write(intTobyte(height,4));//高度
- bos.write(temp2);
- int skip=4-width*3%4;//得到每行要跳过的数字(与windows 系统机制有关)
- if(temp1[10]==55)
- bos.write(new byte[1]);
- for(int i=height-1;i>=0;i--)
- {
- for(int j=0;j<width;j++)
- {
- Color c=new Color(map[i][j]);
- int blue=c.getBlue();
- int green=c.getGreen();
- int red=c.getRed();
- bos.write(blue); //写入之前存放的像素点分量
- bos.write(green);
- bos.write(red);
- }
- if(skip!=4)
- bos.write(new byte[skip]);
- }
- bos.flush();
- }
- } catch (IOException e) {
- }
- }
- //字节转int
- public static int byteToint(byte b[])
- {
- int t1=(b[3]&0xff)<<24;
- int t2=(b[2]&0xff)<<16;
- int t3=(b[1]&0xff)<<8;
- int t4=b[0]&0xff;
- //System.out.println(b[1]&0xff);//输出的是一个整形数据
- //在java中,设计int和比int位数来的小的类型b,如byte,char等,都是先把小类型扩展成int再来运算, ??????
- //return( t1<<24)+(t2<<16)+(t3<<8)+t4;//必须加括号
- return t1+t2+t3+t4;
- }
- public static int byteToint1(byte b[])
- {
-
- int t3=(b[1]&0xff)<<8;
- int t4=b[0]&0xff;
- //System.out.println(b[1]&0xff);//输出的是一个整形数据
- //在java中,设计int和比int位数来的小的类型b,如byte,char等,都是先把小类型扩展成int再来运算, ??????
- //return( t1<<24)+(t2<<16)+(t3<<8)+t4;//必须加括号
- return t3+t4;
- }
- //int 转字节
- public static byte[] intTobyte(int a,int len)
- {
- byte []t=new byte[len];
- t[0]=(byte) ((a&0xff));
- if(len>1)
- t[1]=(byte)((a&0xff00)>>8);
- if(len>2)
- t[2]=(byte)((a&0xff0000)>>16);
- if(len>3)
- t[3]=(byte)((a&0xff000000)>>24);
- return t;
- }
-
- private static void setUIFont(javax.swing.plaf.FontUIResource f) {
- Enumeration<Object> keys = UIManager.getDefaults().keys();
- while (keys.hasMoreElements()) {
- Object key = keys.nextElement();
- Object value = UIManager.get(key);
- if (value instanceof javax.swing.plaf.FontUIResource) {
- UIManager.put(key, f);
- }
- }
- }
- /**
- * LSB处理
- * @author
- * @date 2019年5月3日
- */
- class LSB{
- public boolean hide(boolean flag) {
- FileNameExtensionFilter filter=new FileNameExtensionFilter("文本文档(*.txt)", "txt");
- JFileChooser fileChosser = new JFileChooser();
- fileChosser.setFileFilter(filter);
- fileChosser.setSelectedFile(new File("密钥.txt"));
- fileChosser.setCurrentDirectory(new File("\\"));
- String info="";
- System.out.println("temp1【10】:"+temp1[10]);
- temp1[10]+=1;//将位图数据偏移量增1,作为是否隐入数据的标识,此时temp1[10]=55;
- System.out.println("temp1【10】:"+temp1[10]);
- if(!flag)
- info=infoJt.getText(); //获取的隐藏信息
- else{
- String mingwen = "0"+infoJt.getText();
- info=RSAUtil.encrypt(mingwen,keyPair.getPublicKey() ,"UTF-8");
- System.out.println("加密后:"+info);
- int choose=fileChosser.showSaveDialog(null);
- if(choose==JFileChooser.APPROVE_OPTION)
- {
- selectFile=fileChosser.getSelectedFile();
- try {
- FileOutputStream fos=new FileOutputStream(selectFile);
- BufferedOutputStream bos=new BufferedOutputStream(fos);
- byte[] N=keyPair.getPublicKey().getN().toString().getBytes();
- byte[] privateKey=keyPair.getPrivateKey().getA().toString().getBytes();
- String maohao=":";
- byte[] mao=maohao.getBytes();
- length_byte_sum=N.length+privateKey.length+maohao.getBytes().length;
- System.out.println("length_byte_sum"+length_byte_sum);
- byte[] len=String.valueOf(length_byte_sum).getBytes();
- // bos.write(length_byte_sum);
- bos.write(len);//
- bos.write(N);//
- bos.write(mao);//
- bos.write(privateKey);//
- bos.flush();
- fos.close();
- } catch (IOException e) {
-
- }
-
- }else{
- return false;
- }
-
-
- }
-
- infoJt.setText("保存成功!");
- System.out.println("隐藏信息:"+info);
- char[] infoChs=info.toCharArray(); //将隐藏信息转成字符数组
- System.out.println("隐藏信息长度"+infoChs.length);
- int index=0,endIndex=0;
- String str;
- byte[] infoBins=new byte[infoChs.length*16];//隐藏信息的字符长度
- // 二进制字符串 一个字符两个字节 转成16位
- char[] wordBins=new char[16];
- // 字节二进制字符串
- //将每个字节隐藏的信
- for(int i=0;i<infoChs.length;i++) {
- str=intToWordBinaryString((int)infoChs[i]);
- //把int转成二进制字符串 7 111
- //把每个字符都转成对应的二进制字符串
- // System.out.print(str+" ");
- str=new StringBuilder(str).reverse().toString();
- // System.out.print(str+" ");
- //reverse 逆序转换 123 321
- wordBins=str.toCharArray();
- for(int j=0;j<16;j++) {
- // System.out.print(wordBins[j]);
-
- infoBins[index++]=(byte)(wordBins[j]-'0');
- //反向加密
- }
- // System.out.println();
- }
-
- //隐藏信息
- index=0;
- for(int i=height-1;i>0;i--) //hang
- for(int j=0;j<width;j++) //lie
- { //infoBins 已经将信息转换成字节反向的二进制字符串
- if(index<infoBins.length) {
-
- map[i][j]=map[i][j]&0xfffffffe; //int 4个字节
- //数据末尾位清洗为0
- //如果infoBins的位为1则将map对应像素点加1
- if(infoBins[index]==1) {
- map[i][j]+=1;
- }
- // System.out.print(map[i][j]+" ");
- index++;
- }
- else
- {
- map[i][j]=map[i][j]&0xfffffffe;//结束标记,一个字的最低位为0
- endIndex++;
- if(endIndex>=16)
- return true;
- }
- }
-
- return false;
- }
- public String show(boolean flag) throws UnsupportedEncodingException {
- byte temp[] = null;
- FileNameExtensionFilter filter=new FileNameExtensionFilter("文本文档(*.txt)", "txt");
- JFileChooser fileChosser = new JFileChooser();
- fileChosser.setFileFilter(filter);
- fileChosser.setCurrentDirectory(new File("\\"));
- String info="";
- byte[] wordBins=new byte[16];
- int index=0;
- String wordStr="";
- int wordInt;
- outer:for(int i=height-1;i>=0;i--) {
- for(int j=0;j<width;j++) {
- wordBins[index++]=(byte)(map[i][j]&0x00000001);
- if(index>=16) {
- for(int k=15;k>=0;k--) {
- wordStr+=wordBins[k];
- }
- wordInt=Integer.parseInt(wordStr,2);
- if(wordInt!=0) {
- info+=(char)wordInt;
- }else {
- break outer;
- }
- index=0;wordStr="";
- }
- }
- }
- String infor=info;
- String result=null;
- if(!flag)
- result = infor;
- else
- {
- int choose=fileChosser.showOpenDialog(null);
- if(choose==JFileChooser.APPROVE_OPTION)//点击的是确定按钮
- {
- selectFile=fileChosser.getSelectedFile();
- try {
- FileInputStream fis=new FileInputStream(selectFile); //选择文件
- BufferedInputStream bis=new BufferedInputStream(fis); //输入的缓冲区
- byte[] wb=new byte[4];//存放宽度的字节数组
- bis.read(wb);
- System.out.println("wb:::"+ new String (wb));
- int len=Integer.parseInt( new String (wb) );
- temp =new byte[len];
- bis.read(temp);
- bis.close();
- String str= new String (temp);
- String[] sub=str.split(":");
- System.out.println("sub[0]"+sub[0]);
- System.out.println("sub[1]"+sub[1]);
- BigInteger n=new BigInteger(sub[0]);
- BigInteger a=new BigInteger(sub[1]);
- PrivateKey p =new PrivateKey( n,a);
- result=RSAUtil.decrypt(infor, p,"UTF-8");
- } catch (Exception e) {
- }
-
- }
-
- }
- return result;
- }
- private String intToWordBinaryString(int i) {
- StringBuilder sb = new StringBuilder(Integer.toBinaryString(i));
- while (sb.length() < 16) {
- sb.insert(0, "0");
- }
- return sb.toString();
- }
- }
- /**
- * 显示面板
- *
- *
- */
- class MyPanel extends JPanel{
- private static final long serialVersionUID = 1L;
- public void paint(Graphics g) {
- super.paint(g);
- if(map!=null)
- {
- for(int i=0;i<map.length;i++)
- {
- for(int j=0;j<map[i].length;j++)
- {
- g.setColor(new Color(map[i][j]));
- g.drawLine(j+200, i, j+200, i);
- }
- }
- }
- }
- }
- class MyListener implements ActionListener{
- JFileChooser fileChosser;
- //文件选择控件
- MyListener()
- {
- FileNameExtensionFilter filter=new FileNameExtensionFilter("24位位图(*.bmp)", "bmp");
- fileChosser=new JFileChooser();
- fileChosser.setFileFilter(filter);
- fileChosser.setCurrentDirectory(new File("\\"));
- }
- public void actionPerformed(ActionEvent e) {
- JMenuItem jmi = (JMenuItem) e.getSource();
- if(jmi==open)//选择的是打开
- {
- open_flag=true;
- int choose=fileChosser.showOpenDialog(null);
- if(choose==JFileChooser.APPROVE_OPTION)//点击的是确定按钮
- {
- selectFile=fileChosser.getSelectedFile();
- readBMP();
- infoJt.setText("图片打开成功!\n");
- if(temp1[10]==54) {
- infoJt.append("最多可嵌入"+height*width/8+"个字节的信息!\n");
- }else if(temp1[10]==55) {
- infoJt.append("此图片已经隐入有信息!");
- }
- }
- }
- else if(jmi==save)//选择的是保存
- {
- if(open_flag){
- int choose=fileChosser.showSaveDialog(null);
- if(choose==JFileChooser.APPROVE_OPTION)
- {
- selectFile=fileChosser.getSelectedFile();
- writeBMP();
- infoJt.setText("保存成功!");
- }
- }else{
- infoJt.setText("请先打开图片!");
- }
-
- }
- else if(jmi==hide) {
- if(open_flag){
- if(new LSB().hide(false)) {
- infoJt.setText("信息隐入成功!");
- }else{
- infoJt.setText("信息隐入出现不确定问题!");
- }
- }else{
- infoJt.setText("请先打开图片!");
- }
- }
- else if(jmi==hide_rsa) {
- if(open_flag){
- if(new LSB().hide(true)) {
- infoJt.setText("信息隐入成功(RSA加密)!");
- }else{
- infoJt.setText("信息隐入出现不确定问题!");
- }
- }else{
- infoJt.setText("请先打开图片!");
- }
- }
- else if(jmi==show) {
- if(open_flag){
- try {
- String info=new LSB().show(false);
- infoJt.setText("隐入的信息内容\n");
- infoJt.append(info);
- } catch (UnsupportedEncodingException ex) {
- Logger.getLogger(_LSB_BMP.class.getName()).log(Level.SEVERE, null, ex);
- }
- }else{
- infoJt.setText("请先打开图片!");
- }
- }
- else if(jmi==show_rsa) {
- if(open_flag){
- try {
- String info=new LSB().show(true);
- infoJt.setText("隐入的信息内容(RSA解密!)\n");
- infoJt.append(info);
- } catch (UnsupportedEncodingException ex) {
- Logger.getLogger(_LSB_BMP.class.getName()).log(Level.SEVERE, null, ex);
- }
- }else{
- infoJt.setText("请先打开图片!");
- }
- }
- }
- }
- public static void main(String[] args) {
- new _LSB_BMP();
- }
- }
复制代码
全部资料51hei下载地址:
LSB,RSA的图片隐藏技术.zip
(41.2 KB, 下载次数: 11)
|