YIXUNFE / blog

文章区
151 stars 25 forks source link

认识图片隐写 #75

Open YIXUNFE opened 8 years ago

YIXUNFE commented 8 years ago

认识图片隐写

大家都知道在信息经过加密算法处理后,我们一般是无法直接从信息中读取内容,而且信息看上去并不那么合理。对于专注信息安全的人,可以从信息的奇怪的外观上判断信息是否经过加密处理,这无疑是此地无银三百两,招着人家来破解你的加密内容。

而今天要介绍的图片隐写则更像是“大隐隐于市”的一种技巧。


隐写术

图片隐写正是隐写术的一种。隐写术的英文是Steganography,来源于15世纪一个德国修道士特里特米乌斯(Trithemius)写的一本讲述密码学和隐写术的著作《Steganographia》。该书书名源于希腊语,意为“隐秘书写”。

隐写术的使用可以追溯到希腊时代。当时有个人为了能够将机密信息传递给盟友,于是将一个奴隶的头发剃光,然后在头皮上写上内容,等到奴隶的头发长长后就派他去送信。盟友只需要再将这个奴隶的头发剃光即可获得信息。

隐写术不同于密码术,它更为隐蔽。密码术虽然无法让人直接获取到内容,但是在信息的传递过程中可以被一眼辨认出与正常内容的区别。而隐写术可以说是侧重在隐蔽信息的传递过程。


图片的隐写

我们来看这张图

canvas

有兴趣的同学可以保存图像后面会用到这张图

好像就是一张平白无奇的纯蓝色的图,是吗?其实这张图片中还隐藏了一张图。在讲述如何破解隐写内容之前,我们先聊聊是如何将另一张图片隐写入原图的。

像素通道

我们知道一张图片是由许多的像素点组成的。每个像素点包含4个通道,即 R(红)G(绿)B(蓝)A(透明度),我们就从通道上做手脚。

最简单的,由于肉眼无法区分像素值上的细微差距。可能有自称像素眼的同学不信,你有看出下图的颜色区别吗?

canvas2

可以下载后用 PS 取色看看

既然我们无法区分像素值的细微差别,那么利用这点,我们就可以实现图片隐写术了。

我们看下被隐写的图片:

logo

logo 大法好

可以看见这张图中的颜色并不是很丰富,大约只有三种颜色。那么我们通过 PS 取色,可以获得如下三个颜色:

rgba(29, 122, 217, 255)
rgba(255, 255, 255, 255)
rgba(255, 111, 0, 255)
PS 里面透明度是 0 -100%, 对应 imageData 中的数值 为 0 到 255

然后将原图像素的红通道的个位数拍扁(置 0),这里以 canvas 获取的 imageData 对象为例:

// 循环原图的 imageData.data
for (i; i < l; i += 4) {
  imgData[i + 0] = (imgData[i + 0] / 10 | 0) * 10
}

由于取色器获取的颜色与被隐的图像上的颜色会有细微差别,所以设置一个阈值做容差处理。在循环原图像素的同时,获取被隐图像上对应点的像素值,进行比对:

for (i; i < l; i += 4) {
  if (
    Math.abs(simgData[i + 0] - 29) < yu &&
    Math.abs(simgData[i + 1] - 122) < yu &&
    Math.abs(simgData[i + 2] - 217) < yu
  ) {
    imgData[i + 0] = (imgData[i + 0] / 10 | 0) * 10
  } else if (
    Math.abs(simgData[i + 0] - 255) < yu &&
    Math.abs(simgData[i + 1] - 255) < yu &&
    Math.abs(simgData[i + 2] - 255) < yu
  ) {
    imgData[i + 0] = (imgData[i + 0] / 10 | 0) * 10 + 1
  } else if (
    Math.abs(simgData[i + 0] - 255) < yu &&
    Math.abs(simgData[i + 1] - 111) < yu &&
    Math.abs(simgData[i + 2] - 0) < yu
  ) {
    imgData[i + 0] = (imgData[i + 0] / 10 | 0) * 10 + 2
  } else {
    imgData[i + 0] = (imgData[i + 0] / 10 | 0) * 10 + 3
  }
}

我们在代码中将被隐图像的颜色对应为原图红通道中的个位数数值:

我们的图片隐写术就这么完成了。最后产生的就是上面看着像纯蓝色的图像。

可以看出,隐写图片的关键是提取被隐图片的颜色值,然后通过一些方法将颜色值放入原图的通道数值中。上面的例子中将红通道的个位数置 0 后,可以隐入 10 中色彩的图片,如果隐入的图片色彩更丰富,可能动用更多的通道,或者通过其他的一些算法达到目的。


提取被隐图片

接下来的工作就是来进行对被隐图片的提取了。首先将上一部生成的图像数据进行循环,然后取出每个像素的红通道数值。

for (i; i < l; i += 4) {
  temp = imgData[i + 0] % 10
  if (temp === 0) {
    simgData[i + 0] = 29
    simgData[i + 1] = 122
    simgData[i + 2] = 217
  } else if (temp === 1) {
    simgData[i + 0] = 255
    simgData[i + 1] = 255
    simgData[i + 2] = 255
  } else if (temp === 2) {
    simgData[i + 0] = 255
    simgData[i + 1] = 111
    simgData[i + 2] = 0
  } else {
    simgData[i + 0] = 29
    simgData[i + 1] = 122
    simgData[i + 2] = 217
  }
}

最后我们将获得的像素数据展示出来:

canvas3

好像有点失真,不过没关系,图像中的主要信息并没有丢失,我们可以清晰的看到图像中的字样。


商业用途

通常我们会将自己产品的图片加上水印,以注明图片来源并避免被他人用于营利性目的。但是由于加上水印后可能会对图片产生副作用。比如遮挡了图片中信息、水印本身的美感等问题,可能会对用户造成一定的心里落差,毕竟这不是用户的原图。

而通过将产品 logo 隐写入原图的方式,就显得更加优雅,即满足了用户对上传图像的期望,又确保了在被其他商业产品盗用图片时的维权手段。



查看DEMO



参考文章

http://www.guokr.com/article/3741/


Thanks


zcyzcy88 commented 8 years ago

打个广告:https://github.com/zcyzcy88/Polytor

YIXUNFE commented 8 years ago

@zcyzcy88 有点意思

ystarlongzi commented 8 years ago