Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

认识图片隐写 #75

Open
YIXUNFE opened this issue Apr 25, 2016 · 3 comments
Open

认识图片隐写 #75

YIXUNFE opened this issue Apr 25, 2016 · 3 comments

Comments

@YIXUNFE
Copy link
Owner

YIXUNFE commented Apr 25, 2016

认识图片隐写

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

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


## 隐写术

图片隐写正是隐写术的一种。隐写术的英文是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 表示颜色 rgba(29, 122, 217, 255);
  • 1 表示颜色 rgba(255, 255, 255, 255);
  • 2 表示颜色 rgba(255, 111, 0, 255);
  • 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
Copy link

@YIXUNFE
Copy link
Owner Author

YIXUNFE commented Apr 26, 2016

@zcyzcy88 有点意思

@ystarlongzi
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants