1.定义
隐写术算是一种加密技术,权威的 wiki 说法是“ 隐写术是一门关于信息隐藏的技巧与科学,所谓信息隐藏指的是不让除预期的接收者之外的任何人知晓信息的传递事件或者信息的内容。”
example:
钱图(钱上面的水印)
个人理解类似加水印logo的做法,加密logo就是盲水印(加密水印)~~~ 上图:
2.用途
上面 的水印就叫做盲水印,隐藏式的水印是以数字数据的方式加入音频、图片或影片中,但在一般的状况下无法被看见。隐藏式水印的重要应用之一是保护版权,期望能借此避免或阻止数字媒体未经授权的复制和拷贝。
1.不同人加相同水印
声明版权
应用案例:
-
某些画师、摄影师、设计师会在其作品中加入水印。
13年左右有位自称是“超写实主义”的画家,声称自己纯手工画的画写实程度可以 超过摄影机,并开办培训班敛财。 后被一位加了盲水印的摄影师戳穿,原来其“画作”都是直接将照片用ps处理成手绘质感的图。
-
淘宝防盗图功能
淘宝卖家图会被淘宝自动打上水印,如果有别的卖家存图作为自己的图上传会被检测出。
2.不同人加不同水印
将某份保密数字资料发送给不同人时,可加上不同标识,如果资料被复制、传播可根据解码出的唯一标识来追究责任人。
应用案例:
-
电影刚刚公映时,每个影院,影厅的 电影底片里都会加入不同的不可见水印, 如果电影流出,就可追究相关影院责任。
-
国内大厂的内部论坛、平台会在HTML页面中加入足够数量 及不被发现的唯一标识。当有内部敏感信息通过截图等方式流出,也可追踪到个人。
example:
2018年7月5日,因涉嫌窃取商业秘密,国家“千人计划”专家、通用电气主任工程师(Principal Engineer at GE Power)***被FBI逮捕。值得注意的是***的“隐写术”:通过对电脑上的数据文件进行加密,然后将机密文件隐藏在一张“日落”的数码照片的代码中,他巧妙避开了公司限制。
类似图种
3.原理图
4.盲水印特性
隐蔽性
由于不希望被察觉、不希望干扰用户体验、不希望被模仿等等原因,我们的水印不可见,也就是隐匿性。
强健性
强健性通常也被称作鲁棒性,简单地说就是耐操性。
需要说明的一点是,鲁棒性和隐蔽性通常不可兼得。
不易移除性
不易移除性跟鲁棒性有些相似, 不同的是:
鲁棒性更加强调的是数字资源在传播过程中不要被不自觉地干扰和破坏。
不易移除性是在别有用心者察觉了盲水印的存在后,不被他们自觉地移除或者破坏。
明确性
没什么可说的,就是盲水印需要表示出明确的信息。
5.前端角度分析
聚焦到载体为图片的隐写术,一起来从前端角度分析其技术原理。
我们知道图片的像素信息里存储着 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 来演示一下。
6.代码分析
1)解密
下面是一张靓女照片,里面的 G 通道分量被我加入了些文本信息,下面通过canvas来解开其中的信息 。
1.首先在页面加入一个 canvas 标签,并获取到其上下文。
<canvas id="canvasShow" width="601" height="338"></canvas>
let ctx = document.querySelector('#canvasShow').getContext('2d')
2.接着将图片先绘制在画布上,然后获取其像素数据。
let img = new Image()let originalDataimg.onload = function () {ctx.drawImage(img, 0, 0)// 获取指定区域的canvas像素信息originalData = ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height)console.log('加料图信息', originalData)processData(originalData)}img.src = require('../assets/下载.png')
打印出数据,会看到有一个非常大的数组。
这个一维数组存储了所有的像素信息,一共有 601 * 338* 4 = 812552个值。其中 4 个值一组,为什么呢?在浏览器中解析图片,除了 RGB 值外,每组第 4 个值为透明度值,即像素信息实际为大家熟知的 rgba 值。
这里的解密规则是对 G 通道进行处理,G 的分量最低位为 1 则该像素设为绿色,G 的分量最低位为 0 则该像素设为黑色,直接看代码实现,完成后我们再绘制到 canvas,即可看到结果。
let processData = function (originalData) { let data = originalData.data for (let i = 0; i < data.length; i++) { if (i % 4 == 1) { if (data[i] % 2 === 0) { data[i] = 0 } else { data[i] = 255 } } else if (i % 4 === 3) { // alpha通道不做处理 continue } else { // 关闭其他分量,不关闭也不影响答案,甚至更美观 // data[i] = 0 } } // 将结果绘制到画布 ctx.putImageData(originalData, 0, 0) }
在 img onload 事件中调用 processData 方法,就可以看到结果啦。
得到的结果可能是这个样子的。
2)加密(在图片中隐藏信息)
上面先说了解密过程,再来反向说说加密过程。
1.首先在页面加入2个 canvas 标签,并获取到其上下文,这两个canvas分别对应文字和原图。
<canvas id="canvasText" width="601" height="338"></canvas>
<canvas id="canvas" width="601" height="338"></canvas>let ctx = document.querySelector('#canvas').getContext('2d')
let ctxText = document.querySelector('#canvasText').getContext('2d')
2.既然要在图片中加入文字信息,那么首先要获取文字的像素信息,这里我先用 canvas 在画布上打印文字,获取像素信息。
let textData ctxText.font = '30px Microsoft Yahei'
ctxText.fillText('hello world!', 300, 260)
textData = ctxText.getImageData( 0, 0, ctxText.canvas.width, ctxText.canvas.height ).data console.log('加密信息data', textData)
3.先保存文字的像素信息,接着加载图片获取其像素信息,然后对两组像素进行处理,过程中我抽离了一个公共方法。
let mergeData = function (newData, color) {let oData = originalData.dataconsole.log('oData', oData)// bit[r,g,b,a]位置, offset+bit =a位置let bit, offsetswitch (color) {case 'R':bit = 0offset = 3breakcase 'G':bit = 1offset = 2breakcase 'B':bit = 2offset = 1break}for (let i = 0; i < oData.length; i++) {if (i % 4 == bit) {// 只处理目标通道if (newData[i + offset] === 0 && oData[i] % 2 === 1) {// 没有水印信息的像素,将其对应通道的值设置为偶数(奇变偶不变)if (oData[i] === 255) {oData[i]--} else {oData[i]++}} else if (newData[i + offset] !== 0 && oData[i] % 2 === 0) {// 有水印信息的像素,将其对应通道的值设置为奇数(偶变奇不变)if (oData[i] === 255) {oData[i]--} else {oData[i]++}}}}ctx.putImageData(originalData, 0, 0)}
这个公共方法做的是,接受要隐藏的数据以及隐藏的颜色通道,然后对原图进行操作,修改图片该通道分量的最低位,如果有文字信息,则最低位置为 1,否则为 0。我们知道,RGB 的三个通道可以分别隐藏不同信息。
4.在 img.onload 中调用 mergeData(textData, 'G'),处理好图像后,只要在浏览器中的 canvas 上右键保存图片就可以得到一张带有加密信息的图片。
let img = new Image()let originalDataimg.onload = function () {ctx.drawImage(img, 0, 0)// 获取指定区域的canvas像素信息originalData = ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height)console.log(originalData)mergeData(textData, 'G')}img.src = require('../assets/20220721180808.jpg')
结尾
例子比较简单,只展示了基本的最低位隐藏文本信息,像二维码这些简单图形也可以这么处理~~~