盲水印就是把一段信息——通常是文字,或者另一张图——藏在一张”载体”图片里,藏完之后图片看上去和原图一模一样。这里的”盲”指的是不需要原图也能恢复:拿到含水印的图片,直接丢给提取工具就能把数据读出来。

想知道这背后是怎么做到的?原理其实非常直觉,30 秒就能用 Piick 盲水印工具 亲自试一下。

明信片与邮票的类比

想象一张明信片:正面是图片——这就是我们的”载体图”。你想藏的那段信息就是一张小小的纸条,塞到邮票底下——没掀开之前谁也看不见。盲水印对数字图片做的就是同样的事:把纸条藏到眼睛不会去检查的那一层里。

LSB:把比特藏进”噪声”里

每张彩色图片的像素都包含三个通道——R、G、B——每个通道用一个 0255 之间的数字表示该颜色的亮度。

关键洞察:把这个数字 ±1,人眼完全分辨不出。红色从 124 变到 125?看不出来。绿色从 200 变到 201?一模一样。所以如果我们想往每个通道里塞一个 01,只需要把这个数字的最后一位换成我们想要的比特即可。这个最后一位叫做最低有效位,也就是 LSB

一个像素给我们 3 个通道 × 1 bit = 每个像素 3 个秘密比特。一张普通的 500×500 图片有 25 万像素,也就是 75 万比特 ≈ 91 KB 的隐藏空间。够塞一段话,完全不够塞一张 16 MB 的图(后面解释)。

消息是怎么塞进去的

嵌入器把你的消息转换成一串比特流:

  1. 先用 32 个比特写下消息的长度(这样提取器才知道什么时候该停)。
  2. 接着是消息本身的字节。
  3. 剩下的容量用随机比特填满——这样未使用区域不会泄露消息的边界。
  4. 按某个顺序遍历所有像素(可选地被密码打乱),每个像素依次把 R、G、B 的 LSB 替换成比特流中的下一位。

提取消息就是把上面的过程倒着做一遍:先读 32 位得到长度,再读对应数量的字节。

为什么必须是 PNG

如果你把含水印的图片存成 JPEG,水印就消失了。原因是什么?

JPEG 是有损格式——它通过丢弃人眼不容易注意到的细节来缩小文件。把 124 圆整到 123200 圆整到 199、相邻像素取平均……这些操作正好会把我们写进 LSB 的那一点点变化抹掉。可以说 JPEG 在反向擦除我们的水印。

PNG 是无损的——每一个字节进去什么样,出来还是什么样。这就是为什么盲水印工具输出的永远是 PNG,并且只要你还想保留水印,就必须始终用 PNG 保存。

把明信片复印一遍 → 字迹还在。把明信片拍照复印 → 邮戳模糊了。PNG 是原件,JPEG 是复印件。

为什么载体必须够大

LSB 提供 每像素 3 比特 的容量。要塞 1 MB 内容,至少需要 1,000,000 × 8 / 3 ≈ 270 万像素——约 1650×1650 的图。要塞 16 MB(一张带透明通道的 2048×2048 PNG),需要约 4400 万像素 的载体。这就是为什么一张 500×500 的图一上来就报错——它只有 91 KB 容量,小了三个数量级。

把图传到 Piick 工具之后,已加载卡片会实时显示载体的容量,点「隐藏」之前就能看到够不够用。

密码不是真正的加密

如果勾上密码字段,Piick 会用一个从密码派生的确定性序列来打乱像素遍历顺序。没有密码的人提取出来的字节就是噪声——32 位长度头根本解析不出有效数字,所以提取器会直接报告”未找到水印”。

但这只是混淆,不是加密。知道原理的人依然可以一位一位把比特挖出来,离线暴力试密码。日常用途(在公开图里藏段私人备注)绰绰有余,但真正敏感的内容请先做真正的加密

亲自试一下

整套算法只需要几百行 JavaScript,跑在你的浏览器里。打开 Piick 盲水印工具,随便丢一张 PNG 进去,输入一小段文字,点「隐藏」。输出的 PNG 看上去一模一样——但切到「提取」标签,原文会原样回来。

想看手把手操作步骤?请读 《盲水印工具使用指南》


以上所有处理都在你本地浏览器中完成,Piick 不会上传你的图片。