一、隐写原理
LSB隐写原理就是图片中的像素一般是由三种颜色组成,即三原色(红绿蓝)。由这三种原色可以组成其他各种颜色,在png图片的存储中,每个颜色占有8bit,即有256种颜色,一共包含256的三次方颜色,即16777216中颜色。人类的眼睛可以区分约1,000万种不同的颜色,剩下无法区分的颜色就有6777216。
LSB隐写就是修改了像素中的最低位,把一些信息隐藏起来。png图片是一种无损压缩,也只有在无损压缩或无压缩的图片(bmp图片是一种无压缩)上实现LSB隐写,
举例:观察图一、图二,没有发现区别,图二是经过图一加特定的信息(测试123aaa)生成新的图片,使用解密函数,可以重新恢复图片中的信息。
图一
图二
二、实现代码
import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; /** * 图像掩蔽 RGB依次取Bit */ public class ImageMasker { public boolean[] content = null; private BufferedImage image = null; private void setContent(Color color, int start) { content[start] = (1 == color.getRed() % 2); content[start + 1] = (1 == color.getGreen() % 2); content[start + 2] = (1 == color.getBlue() % 2); } private int changeToColor(int rgb, int start) { if (content[start]) rgb = rgb | 0x00010000; else rgb = rgb & 0xFFFEFFFF; if (content[start + 1]) rgb = rgb | 0x00000100; else rgb = rgb & 0xFFFFFEFF; if (content[start + 2]) rgb = rgb | 0x00000001; else rgb = rgb & 0xFFFFFFFE; return rgb; } /** * 读取文件 * @param file * @return */ public boolean read(String file) { try { image = ImageIO.read(new File(file)); } catch (IOException e) { e.printStackTrace(); return false; } int height = image.getHeight(); int width = image.getWidth(); content = new boolean[height * width * 3]; for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { int color = image.getRGB(w, h); Color c = new Color(color); setContent(c, (h * width + w) * 3); } } return true; } public boolean write(String file) { if (null == image) return false; int height = image.getHeight(); int width = image.getWidth(); for (int h = 0; h < height; h++) for (int w = 0; w < width; w++) image.setRGB(w, h, changeToColor(image.getRGB(w, h), (h * width + w) * 3)); try { String format = file.substring(file.lastIndexOf(".") + 1); ImageIO.write(image, format, new File(file)); } catch (IOException e) { e.printStackTrace(); return false; } return true; } }
public class StringEncoder { public static boolean[] encodeString(String s) { try { byte[] b = s.getBytes("GBK"); boolean[] bl = new boolean[b.length * 8]; for (int i = 0; i < b.length; i++) { bl[8 * i] = (((b[i]) & 0x1) == 1); bl[8 * i + 1] = (((b[i] >> 1) & 0x1) == 1); bl[8 * i + 2] = (((b[i] >> 2) & 0x1) == 1); bl[8 * i + 3] = (((b[i] >> 3) & 0x1) == 1); bl[8 * i + 4] = (((b[i] >> 4) & 0x1) == 1); bl[8 * i + 5] = (((b[i] >> 5) & 0x1) == 1); bl[8 * i + 6] = (((b[i] >> 6) & 0x1) == 1); bl[8 * i + 7] = (((b[i] >> 7) & 0x1) == 1); } return bl; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 根据隐写的图片,还原写入的数据 * @param bl * @return */ public static String decodeString( boolean[] bl) { try { byte[] b = new byte[bl.length / 8]; for (int i = 0; i < b.length; i++) { b[i] = 0; if (bl[8 * i]) b[i] = (byte) (b[i] | 0x1); if (bl[8 * i + 1]) b[i] = (byte) (b[i] | 0x2); if (bl[8 * i + 2]) b[i] = (byte) (b[i] | 0x4); if (bl[8 * i + 3]) b[i] = (byte) (b[i] | 0x8); if (bl[8 * i + 4]) b[i] = (byte) (b[i] | 0x10); if (bl[8 * i + 5]) b[i] = (byte) (b[i] | 0x20); if (bl[8 * i + 6]) b[i] = (byte) (b[i] | 0x40); if (bl[8 * i + 7]) b[i] = (byte) (b[i] | 0x80); } return new String(b, "GBK"); } catch (Exception e) { e.printStackTrace(); } return "Failed to decode string!"; } }
三、测试
public static void main(String[] args) { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); ImageMasker imageMasker = new ImageMasker(); try { System.out.print("请输入文件路径:"); String s = bufferedReader.readLine(); imageMasker.read(s); System.out.print("写入内容还是读取(r/w):"); s = bufferedReader.readLine(); if (s.equals("r")) { System.out.print(StringEncoder.decodeString(imageMasker.content).trim()); } else if (s.equals("w")) { System.out.print("输入数据(<=" + imageMasker.content.length / 8 + "):"); s = bufferedReader.readLine(); boolean[] value = StringEncoder.encodeString(s); for (int i = 0; i < value.length; i++) imageMasker.content[i] = value[i]; for (int i = value.length; i < imageMasker.content.length; i++) { imageMasker.content[i] = false; } System.out.print("输出文件路径:"); s = bufferedReader.readLine(); imageMasker.write(s); } } catch (IOException e) { e.printStackTrace(); } }
图一:C:\Users\Administrator\Desktop\sorce.png
图二:C:\Users\Administrator\Desktop\sorce1.png
中文乱码,大家帮我看看原因。
来自网络,可以试试
import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Scanner; public class LSB { public static void main(String[] args) throws IOException { Scanner scan = new Scanner(System.in); System.out.println("***************LSB图像隐写编码程序****************"); System.out.println("*********请选择想要使用的功能(输入数字)*************"); System.out.println(); System.out.println("******************1.LSB编码********************"); System.out.println("******************2.LSB解码********************"); System.out.println(); System.out.print("请输入你想选择的功能:"); String choice = scan.next(); switch (choice) { case "1": System.out.print("请输入需要加密的文件的路径:"); String textPath = scan.next(); System.out.print("请输入png图像辅助文件的路径:"); String imagePath = scan.next(); System.out.print("最后再输入一下生成的png图片的保存路径:"); String imageOutputPath = scan.next(); LSBEncoder(textPath, imagePath, imageOutputPath); scan.close(); break; case "2": System.out.print("请输入待解码的png图片的路径:"); String imageInputPath = scan.next(); System.out.print("请输入解码后,存放数据的文件名称"); String textFilePath = scan.next(); LSBDecoder(imageInputPath, textFilePath); scan.close(); break; default: System.out.print("谁叫你乱按键盘的啊!!!活该不能执行,哼?!"); scan.close(); break; } } public static void LSBEncoder(String textPath, String imagePath, String imageOutputPath) throws IOException { //读取png图像 BufferedImage image = ImageIO.read(new File(imagePath)); int width = image.getWidth(); int height = image.getHeight(); int[][][] rgb = new int[width][height][3]; //将图像每个点的像素(R,G,B)存储在数组中 for (int w = 0; w < width; w++) { for (int h = 0; h < height; h++) { int pixel = image.getRGB(w, h);//读取的是一个24位的数据 //数据三个字节分别代表R、B、G rgb[w][h][0] = (pixel & 0xff0000) >> 16;//R rgb[w][h][1] = (pixel & 0xff00) >> 8;//B rgb[w][h][2] = (pixel & 0xff);//G } } //导入待加密机密文件 FileInputStream fis = new FileInputStream(textPath); int byteLen = fis.available(); byte[] buf = new byte[byteLen]; fis.read(buf); //我用两个字节(16位)表示数据部分的长度,也就是说,图像转像素点后,前16个像素点的R对应的字节的最低有效位都是默认表示数据字节数组的长度 //规定,数据的长度最大为2^16-1 int[] bufLen = new int[2]; bufLen[0] = (byteLen & 0xff00) >> 8; bufLen[1] = (byteLen & 0xff); for (int i = 0; i < 2; i++) { for (int j = 7; j >= 0; j--) { int h = (i * 8 + (7 - j)) / width; int w = (i * 8 + (7 - j)) % width; //只取每个像素点的R,的字节的最低位 if ((bufLen[i] >> j & 1) == 1) { rgb[w][h][0] = rgb[w][h][0] | 1; } else { rgb[w][h][0] = rgb[w][h][0] & 0xe; } } } //按照规则将数据的二进制序列全部放到每一个像素点的第一个字节的最后一位上 for (int i = 2; i < byteLen + 2; i++) { for (int j = 7; j >= 0; j--) { //高 int h = (i * 8 + (7 - j)) / width; //宽 int w = (i * 8 + (7 - j)) % width; if ((buf[i - 2] >> j & 1) == 1) { rgb[w][h][0] = rgb[w][h][0] | 1;//变1 } else { rgb[w][h][0] = rgb[w][h][0] & 0xe; } } } // 构建通过编码生成的png图片的类型 BufferedImage imageOutput = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int w = 0; w < width; w++) { for (int h = 0; h < height; h++) { int[] color = new int[3]; color[0] = rgb[w][h][0] << 16; color[1] = rgb[w][h][1] << 8; color[2] = rgb[w][h][2]; int pixel = 0xff000000 | color[0] | color[1] | color[2]; imageOutput.setRGB(w, h, pixel); } } ImageIO.write(imageOutput, "png", new File(imageOutputPath)); } public static void LSBDecoder(String imageInputPath, String textFilePath) throws IOException { BufferedImage imageInput = ImageIO.read(new File(imageInputPath)); int width = imageInput.getWidth(); int height = imageInput.getHeight(); int[] bufLen = new int[2]; //将图像每个点的像素(R,G,B)存储在数组中 for (int i = 0; i < 2; i++) { int[] bits = new int[8]; for (int j = 7; j >= 0; j--) { int h = (i * 8 + (7 - j)) / width; int w = (i * 8 + (7 - j)) % width; int pixel = imageInput.getRGB(w, h); int r = (pixel & 0xff0000) >> 16; bits[j] = (r & 1) << j; } bufLen[i] = bits[7] | bits[6] | bits[5] | bits[4] | bits[3] | bits[2] | bits[1] | bits[0]; } int byteLen = ((bufLen[0] << 7) | bufLen[1]); byte[] buf = new byte[byteLen]; for (int i = 2; i < byteLen + 2; i++) { int[] bits = new int[8]; for (int j = 7; j >= 0; j--) { int h = (i * 8 + (7 - j)) / width; int w = (i * 8 + (7 - j)) % width; int pixel = imageInput.getRGB(w, h); int r = (pixel & 0xff0000) >> 16; bits[j] = (r & 0x1) << j; } buf[i - 2] = (byte) (bits[7] | bits[6] | bits[5] | bits[4] | bits[3] | bits[2] | bits[1] | bits[0]); } FileOutputStream fos = new FileOutputStream(textFilePath); fos.write(buf); fos.close(); } }