隐写术:在其他事物中隐藏事物的艺术与科学 – 第三部分
这是四部分系列的第三部分。在第一部分中,我们涵盖了图像格式的基础知识,并找到了在图像中隐藏数据的位置。在第二部分中,我们实际编写了代码,将数据隐写编码到图像中,然后提取它,而不使图像看起来不同。
压缩带来的挑战
在第二部分中,我们遇到了一个问题——压缩。当图像被压缩时,我们精心隐藏的数据可能会被损坏。我们通过将消息中的每一位重复9次来修复了这个问题,但我们可以做得更好。本文将介绍如何使用一些数学方法,以纠错码的形式,在每个图像中隐藏更多的数据位。
我们的朋友维基百科给出了纠错码的良好定义:
“纠错码(ECC)是向消息中添加冗余数据的过程,这样即使引入了一定数量的错误(最多达到所用代码的能力),接收方也可以恢复消息。”
我们之前使用的重复是最简单的ECC——我们发送冗余数据,一遍又一遍地重复自己,这样即使某些位是错误的,我们仍然可以接收消息。
定义问题
在我们开始寻找更好的方法之前,我们需要定义我们的问题。我们知道压缩会破坏数据,但具体是如何破坏的?
首先,我们可以说压缩引入了噪声——换句话说,有些位不是我们期望的那样。例如,我们可能输入消息1101
,但输出1100
——在这种情况下,我们会说最后一位从1“翻转”到了0。既然我们知道存在一些噪声,我们如何定义它?
我们有几个选择。你可以阅读论文,比如这篇8页的论文,由舰队信息战中心的某人撰写,或者你可以做一些假设并看看它们是否有效。
让我们折中一下,做出一些基于现实的假设。它们可能不是100%正确,但足够正确。因此,对于我们的目的,我们可以假设:
- 随机、位置无关的噪声:噪声中没有特定的模式;任何给定的位被翻转的机会与任何其他相同重要性的位相同。这意味着在图像中编码数据的位置无关紧要;图像左下角的像素(例如)与中间的像素一样可能被更改。
- 更重要的位噪声更少:任何像素的最低有效位最有可能被翻转,因为压缩试图消除无用数据,并且位被翻转的机会随着位变得更重要而减少。
- 噪声是二进制对称的:这是一种花哨的说法,表示噪声是无偏且均匀分布的——零被翻转的可能性与一相同。
与其花费大量时间从会被压缩的网站上上传和下载图像,我们可以通过随机化图像中的一些位来模拟这种类型的噪声。这让我们可以使用代码,这很好且可控,因此我们可以轻松进行大量测试。
这是我们的图像在模糊之前和之后:
它可能看起来没有太大不同,但它将we’re going to hide this message in an image!
变成了we’re eking to hide this message in an image
(结尾没有感叹号),所以它肯定造成了一些损害。
纠错码入门
现在的挑战是能够从模糊的图像中提取数据,即使存在噪声。第二部分的重复方法有效,但必须将每一位重复9次意味着我们无法传输足够的数据。我们可以做得更好。
在我们深入探讨纠错之前,我们需要谈谈奇偶校验位。本质上,奇偶校验位向任何给定的二进制字符串添加一个检查,告诉我们字符串中1位的数量是否为奇数。例如:字符串000
没有1位,因此其奇偶校验位为0(留给我们最终的字符串0000
)。相反,字符串010
有奇数个1位,因此其奇偶校验位为1,留给我们0101
。你会注意到,一旦添加了奇偶校验位,这意味着每个字符串都有偶数个一。
过程是:
- 根据你的消息计算奇偶校验位。如果它有奇数个
1
值,奇偶校验位是1
,否则奇偶校验位是0
。 - 将这些奇偶校验位放入消息中;它们通常放在末尾,但只要接收方知道在哪里找到它们,它们的位置实际上并不重要。
我们为什么这样做?简单——如果字符串中的某个位被翻转,我们可以通过检查奇偶校验位来发现有问题(如果上面的字符串0101
变成了1101
,我们知道110
的奇偶校验位应该是0——但它是1,所以一定有问题)。不幸的是,这只能告诉我们有问题——而不是什么问题。我们无法知道哪个位被翻转了。
汉明码:检测与纠正错误
进入理查德·汉明,他发明了汉明码,一种结合奇偶校验位来检测和纠正错误的方法。等等,这即将变得非常棒(而且非常数学化)。
对于下一部分,让我们假设你试图发送4位数据,并且你(以某种方式)知道其中不会有多于一个错误。
假设你有通常的奇偶校验位和4位数据。如果有错误,你怎么能告诉哪个位错了?你不能——四个中的一个被翻转了,但无法知道是哪一个。(请注意,这些图形中的圆圈每个代表一个奇偶组——一旦所有奇偶校验位被计算并添加,每个组都会有偶数个一。这意味着如果我们接收消息时一个圆圈没有偶数个一,我们就知道有问题。)
如果你只是添加另一个奇偶校验位,那很好,但你所做的只是重复了奇偶校验位——这很有用,因为这意味着你可以通过检查另一个来告诉其中哪一个错了。但这似乎主要是浪费。如果出现问题,你仍然无法告诉哪些数据位被翻转了——第二个奇偶校验位没有提供新信息。
让我们尝试一些奇怪的事情。如果你从第一个奇偶校验位中排除第4个数据位会怎样?如果前三个数据位中的任何一个被翻转,第一个奇偶校验位会告诉我们,如果它们中的任何一个被翻转,第二个奇偶校验位会像往常一样告诉我们,但是……如果第一个奇偶校验位错了,可能是前三个数据位中的任何一个错了,但如果第二个奇偶校验位错了,可能是四个中的任何一个。看起来我们在这里有点缩小范围了。
如果我们再进一步,不断缩小第一个奇偶校验位覆盖的位数,我们得到下面的布局。我们在这里做了一件不可思议的事情!如果两个奇偶校验位都错了,我们知道第一个数据位错了——不仅仅是“一个位”,而是确切地知道哪个位错了!
如果你进一步推进,你可以到达这里:
说真的,花一分钟看看上面的图片。这意味着如果任何数据位被翻转,你可以告诉是哪一个并修复它,因为你知道每个圆圈必须包含偶数个一。通过扩展这个想法,你可以玩转奇偶校验位和数据位的数量来处理不同级别的错误—— broadly speaking,更多的奇偶校验位帮助你纠正更多的错误,但这意味着你用于数据的空间更少。
如果纠错码仍然令人困惑,这样想——我们创建了一堆接收无效的潜在消息,这样如果你收到它们,你就知道有问题(有效消息被称为“码字”——“纠错码”中的“码”)。当你收到一个你知道错误的消息时,你可以通过选择与你收到的内容最相似的有效码字来纠正错误(这意味着我们希望码字尽可能不同,以便任何给定的不正确消息应该是什么码字是明显的)。
这是纠正错误的数学基础,比我们之前使用的简单重复要高效得多——在我们的案例中,我们可以容纳四倍以上的数据量!下次,我们将把这些数学付诸实践。
达科他运营Striker Security——你可以在https://strikersecurity.com/blog找到更多他的写作