什么是隐写术
隐写术是一门关于信息隐藏的技巧与科学,所谓信息隐藏指的是不让除预期的接收者之外的任何人知晓信息的传递事件或者信息的内容。来源于的一本讲述密码学与隐写术的著作。
隐写的信息通常用一些传统的方法进行加密,然后用某种方法修改一个“伪装文本”(covertext),使其包含被加密过的消息,形成所谓的“隐秘文本”(stegotext)。例如,文字的大小、间距、字体,或者掩饰文本的其他特性可以被修改来包含隐藏的信息。只有接收者知道所使用的隐藏技术,才能够恢复信息,然后对其进行解密。
一些常见的以图像为载体的隐写软件
图片隐写术 & LSB算法
图片隐写术
通过对图片进行处理,通过处理在其中加入的“隐秘信息”,并且肉眼识别不出来。
这里先了解一下图片大致分类 图片可以分为两类:
一个是空间域 (spatial domain),常见的是.png .bmp。另一个是频率域(frequency domain),常见的是.jpg。空间域中,图片一般是由颜色的强度表现的(color intensity),频率域中,图片一般由颜色的频率表现的(color frequency)。
png图片是一种无损压缩的位图片格式,也只有在无损压缩或者无压缩的图片(BMP)上实现LSB隐写;而因为jpg图片对像数进行了有损压缩,我们对图片进行的处理可能会在压缩的过程中被破坏。
LSB算法
LSB全称为 Least Significant Bit(最低有效位),是一种常被用做图片隐写的算法)。LSB属于空域算法中的一种,是将信息嵌入到图像点中像素位的最低位,以保证嵌入的信息是不可见的。
如下图所示,我们将#67C23A用二进制表示出三原色每个颜色通道对应的二进制数值,红色框框选出的为对应的最低有效位。
实现原理
图片与像素
首先我们先了解一下日常中的图片及其组成图片的像素。
我们的PNG和BMP图片中的图像像素一般是有由RGB三原色组成。
eg:一张300px * 300 px,对应的就有 300 * 300 = 90000 个像素点。
每个点的颜色都是由RGB三种颜色组成的,每一种颜色的范围是0-255,用十六进制表示取值范围为0x00-0xFF; 用二进制表示,就是八位。
也就说一共有2^8 * 2^8 * 2^8 = 2^24 = 16 777 216, 也就是1600多万种颜色,而人类的眼睛可以看见数百万种颜色,说明还有一些颜色是人的肉眼不可见的。
如下面两张图: 我们去更改#67C23A的R,G,B通道的最低有效位,将1改为0,0改为1,等到的颜色是#66C33B,可以看出渲染出来的两个色块颜色基本看不出差别。
利用canvas实现图片隐写术
了解了以上的内容,我们可以来看下如何利用canvas去实现图片隐写,主要使用canvas以上三个方法:
- drawImage():画布上绘制图像。
- getImageData():获取图像数据。
- putImageData(): 将图像数据放回画布。
- 首先在页面加入一个 canvas 标签,并获取到其上下文:
<canvas id="cat_canvas" width="300" height="300"></canvas>
const catCtx = document.getElementById("cat_canvas").getContext("2d");
- 然后将图片先绘制在画布上,然后获取其像素数据:
const img = new Image();
let originalData;
img.onload = function() {
catCtx.drawImage(img, 0, 0);
// 获取指定区域的canvas像素信息
originalData = catCtx.getImageData(0, 0, catCtx.canvas.width, catCtx.canvas.height);
console.log(originalData.data);
};
img.src = 'cat.png';
我们来看下打印出来的 // console.log(originalData);
是一个一维数组存储了所有的像素信息,一共有 300 * 300 * 4 = 360000 (36万)个值。其中 4 个值一组,为什么呢?在浏览器中解析图片,除了 RGB 值外,每组第 4 个值为透明度值,即像素信息实际是我们熟知的 rgba 值。
我们将这个一维数组每4个为一组,作为rgba值,在浏览器中渲染出这 300 * 300 = 90000 个点,可以复原这张图片。(这里给每个点之间了加了间距)
如果我们对一张真实图片的R通道最低有效位(LSB)进行处理,将R通道的最低有效位原先为0=》1,1=》0,可以对比一下处理前后的效果,其实人眼也是识别不出差距的。
实现图片隐写-加密过程
- 首先通过getImageData()获取原始图片和需隐藏文本的像素数据,分别定义为imageData和textData。
获取文字的像素信息,用 canvas 在画布上打印文字,获取像素信息。
let textData;
textCtx.font = '12px Microsoft Yahei';
textCtx.fillText('生活就像海洋只有意志坚强的人才能到达彼岸', 20, 142);
textData = textCtx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
如下图canvas打印的文本的图片(300 * 300),透明区域的rgba中a通道的数值为0。
2.选择一个隐藏数据的目标通道,eg:R通道;将imageData与textData数据做比对。
修改imageData的数据,如果没有文字信息,将R通道最低位设为0;有文字信息的像素,将R通道最低位设置为1。
具体实现:
3.将处理完图像数据放回画布
const newImgData = this.mergeImgText(imgData, textData, 'R');
textCtx.putImageData(newImgData, 0, 0);
这样我们就得到一张加密完成的图片,如下:
实现原理-解密过程
解密规则是对 R 通道进行处理,R 的分量最低位为 1 则该像素设为红色,R 的分量最低位为 0 则该像素设为黑色,完成后我们再绘制到 canvas。具体的实现:
最终得到隐藏的文本: