LSB(全大写)有时也指Least Significant Byte,指多字节序列中最小权重的字节。指的是一个二进制数字中的第0位(即最低位)
RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。
按照计算,256级的RGB色彩总共能组合出约1678万种色彩,即256×256×256=16777216。通常也被简称为1600万色或千万色。也称为24位色(2的24次方)。
RGB24:使用24位来表示一个像素,RGB24:RGB分量都用8位表示,取值范围为0-255。注意在内存中RGB各分量的排列顺序为:BGR
RGB32:使用32位来表示一个像素,RGB32:RGB分量各用去8位,剩下的8位用作Alpha通道或者不用。(ARGB32就是带Alpha通道的RGB24。)注意在内存中RGB各分量的排列顺序为:BGRA
每个通道可以用8位二进制标识,如下一张图,改变低位第0或第1,肉眼几乎察觉不出颜色的变化。所以,我们可以把信息写入这样的低位
知道了隐写的原理,我们可以考虑多种方式的写入
1、一个像素点至少可以有3个(或更多)通道RGB,这3个通道都可以隐写,写到哪一个里面如果不知道就很难读取。我们可以只写R,只写G,甚至可以三个通道混着写,可能性很多
2、写入字节的低位,后两位人眼都分辨不出来了。是写了后1位,还是写了后2位,还是基数写1位,偶数写2位,这个也有很多种可能
3、结束位和开始位,一张图片像素=宽*高,我从那个位置开始写,从哪个位置结束,是从左到右,还是从右到左。从上到下,还是从下到上。也可以自己定义规则
4、对原文进行混淆、加密,这种情况,在不知道规则或秘钥的情况下,基本是不可能了。
5、因为隐写的是二进制,可以把图片,zip,txt文件,word等各种类型的文件写入。如果不知道原来的格式,读取出来后也无法还原。
下面是一段规则非常简单的隐写代码
1、只在R通道最后1位进行隐写
2、前三个字节Byte,即前24个像素保存隐藏数据的大小len。根据大小,读取后面的len*8个像素的末1位,进行读取
3、数据的读写没有进行加密处理
注意里面几个运算符: | & >> <<
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 LSB1 {
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;//G
rgb[w][h][2] = (pixel & 0xff);//B
}
}
//读取待加密机密文件到字节数组
FileInputStream fis = new FileInputStream(textPath);
int byteLen = fis.available();
byte[] buf = new byte[byteLen];
fis.read(buf);
fis.close();
//我用3个字节(24位)表示数据部分的长度
int[] bufLen = new int[3];
bufLen[0] = (byteLen & 0xff0000 ) >> 16;
bufLen[1] = (byteLen & 0xff00 ) >> 8;
bufLen[2] = (byteLen & 0xff);
for (int i = 0; i < 3; 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] & 0xfe;
}
}
}
//按照规则将数据的二进制序列全部放到每一个像素点的第一个字节的最后一位上
for (int i = 3; i < byteLen + 3; 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-3] >> j & 1) == 1) {
rgb[w][h][0] = rgb[w][h][0] | 1;//二级制:00000001
} else {
rgb[w][h][0] =rgb[w][h][0] & 0xfe; //二级制:11111110
}
}
}
//构建通过编码生成的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();
//前3个字节保存的是信息的长度
int[] bufLen= new int[3];
for (int i = 0; i < 3; 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类型
int byteLen = ( (bufLen[0] << 16)| (bufLen[1] << 8) | bufLen[2]);
//System.out.println(byteLen);
byte[] buf = new byte[byteLen];
//开始循环,读取数据
for (int i = 3; i < byteLen + 3; 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-3] = (byte)(bits[7] | bits[6] | bits[5] | bits[4] | bits[3] | bits[2] | bits[1] | bits[0]);
}
//System.out.println(new String(buf));
//把读取的数据写入文件
FileOutputStream fos = new FileOutputStream(textFilePath);
fos.write(buf);
fos.close();
}
}