上个月在千里码刷题的时候,碰到了比较有意思的一道题——隐写术。既然感觉有意思,又很久没有玩过canvas,所以今天结合这两块内容带大家探索一下。

隐写术算是一种加密技术,权威的wiki说法是“隐写术是一门关于信息隐藏的技巧与科学,所谓信息隐藏指的是不让除预期的接收者之外的任何人知晓信息的传递事件或者信息的内容。”这看似高大上的定义,并不是近代新诞生的技术,早在13世纪末德国人Trithemius就写出了《隐写术》的著作,学过密码学的同学可能知道。好了,说了这么多,隐写术到底是什么技术,让我们看一个例子。

下面是一张看似普通的图片,但其中却藏有另一个肉眼无法识别的图像哦。

这是如果把上图每个色彩空间和数字3进行逻辑与运算,再把亮度增强85倍,可以得到下图。

简单的说,上述的处理过程可以理解为对图片像素的处理,也就是说,加密的信息散布在每个像素点上。可是,13世纪还没有“像素”这个概念吧?!没错,上面这个例子只是隐写术的一个现代技术实现,隐藏信息的手段有很多,我们日常的钞票防伪也算是隐写术的一种,所以标题上也限定了我们的讨论范围——图片隐写术。

{15AAFDB9-59F0-458E-93EF-AE4E799FA5D2}

(电子水印与隐写术有一些共通点)

聚焦到载体为图片的隐写术,一起来从前端角度分析其技术原理。

我们知道图片的像素信息里存储着RGB的色值,R、G、B分别为该像素的红、绿、蓝通道,每个通道的分量值范围在0~255,16进制则是00~FF。在CSS中经常使用其16进制形式,比如指定博客头部背景色为#A9D5F4。其中R(红色)的16进制值为A9,换算成十进制为169。这时候,对R分量的值+1,即为170,整个像素RGB值为#AAD5F4,别说你看不出差别,就连火眼金金的“像素眼”设计师都察觉不出来呢。于此同时,修改G、B的分量值,也是我们无法察觉的。因此可以得出重要结论:RGB分量值的小量变动,是肉眼无法分辨的,不影响对图片的识别。

有了这个结论,那就给我们了利用空间,常用手段的就是对二进制最低位进行操作,下面就用canvas来演示一下。

解开图中的秘密

这是一张我们当家美女小兰师姐的照片,为了让例子足够简单,里面的R通道分量被我加入了文本信息,想知道其中的信息,可以跟我用canvas代码来解开。

首先在页面加入一个canvas标签,并获取到其上下文。

接着将图片先绘制在画布上,然后获取其像素数据。

打印出数据,会看到有一个非常大的数组。

 

QQ截图20160325174947

这个一维数组存储了所有的像素信息,一共有 256 * 256 * 4 = 262144个值。其中4个值一组,为什么呢?在浏览器中解析图片,除了RGB值外,每组第4个值为透明度值,即像素信息实际为大家熟知的rgba值。 

这里的解密规则是对R通道进行处理,R的分量最低位为1则该像素设为红色,R的分量最低位为0则该像素设为黑色,直接看代码实现,完成后我们再绘制到canvas,即可看到结果。

在img onload事件中调用processData方法,就可以看到结果啦。

得到的结果可能是这个样子的。

result

在图片中隐藏信息

讲了基础的解密过程,再来反向说说加密过程。

既然要在图片中加入文字信息,那么首先要获取文字的像素信息,这里我先用canvas在画布上打印文字,获取像素信息。

先保存文字的像素信息,接着加载图片获取其像素信息,然后对两组像素进行处理,我在这里抽离了一个公共方法。

上述代码做的是,接受要隐藏的数据以及隐藏的颜色通道,然后对原图进行操作,修改图片该通道分量的最低位,如果有文字信息,则最低位置为1,否则为0。从最文章开头的结论知道,RGB的三个通道可以分别隐藏不同信息。

在img.onload中调用mergeData(textData, 'R'),处理好图像后,只要在浏览器中的canvas上右键保存图片即可。

这里的例子比较简单,只展示了基本的最低位隐藏文本信息,像二维码这些简单图形也可以这么处理。现实中隐藏画中画则需要更专业的图像处理算法,这里就不再展开了。

应用价值

图片隐写术的应用价值很广泛,比如程序员之间的表白(不限男女),不失为一种浪漫的方式~

上面的案例中我没有放出师姐的原片,这意味着如果盗用上面的图片,我是有办法识别出来的,起到了简单的一种签名作用。当然你也有办法消除掉里面的信息,而前提是你需要知道我的加密方式,可是实际应用中绝不会这么简单哦。有个成功案例就是大众点评通过这种方式,成功证明食神app对其图片的盗用,为自己的合法权益进行了有效维护。

 

好的,感谢阅读到最后,作为回报,我将福利隐藏在了师姐的图片中,请自行发现吧~

原创文章转载请注明:

转载自AlloyTeam:http://www.alloyteam.com/2016/03/image-steganography/

  1. Pingback: 前端的一周mark(2016-03-28~2016-04-03 – 项目经验积累与分享

  2. 加密后的图像直接在canvas上显示 然后我用todataurl()把canvas上已经加密的内容保存成图片再对图片进行解密 结果是全红色的我想问下大神 用todataurl()为什么会出现我描述的情况 canvas的内容还有什么办法压缩保存为图片 使原本的canvas内容不会被改变 并且可以解密成功

  3. originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);报错了 Failed to execute ‘getImageData’ on ‘CanvasRenderingContext2D’: The canvas has been tainted by cross-origin data.

  4. 如果想像你一样实现3个颜色通道均加入信息,代码里面我的改正是在mergeData方法中加入回调函数var mergeData = function(newData, color, callback){}每次处理完一个通道后,得到的orginalData;然后回调函数中再写入新的通道的信息,调用mergeData方法,可是实际解码除了R通道,其他不对……是我回调函数写的不对?