在本教程中,我们将使用Python构建隐写工具。隐写术是将信息隐藏在其他数据中的一种做法。与加密不同,加密的目标是确保两方之间的通信内容的安全,而隐写术则旨在掩盖双方之间根本无法通信的事实。
我们的工具将使用户能够在外观正常的.png图像文件中隐藏秘密文本。图像的接收者将使用相同的工具来显示隐藏的消息。
我们将使用Python来构建该工具。最受欢迎的Python图像处理库是Pillow和OpenCV,但它们是具有许多依赖关系的繁重库。我们将避免这些情况,而是使用轻量级的PyPNG库(该库是用纯Python编写的),因此更易于在各种平台上运行。
让我们想象三个人:爱丽丝,鲍勃和夏娃。爱丽丝想发送私信给鲍勃,夏娃想截取这条消息。虽然现代加密可以帮助Alice和Bob确保Eve不知道其消息的内容,但是Eve仍然可以仅从知道Alice和Bob处于通信状态以及它们通信的频率来推断出有趣的信息。
为了完全掩盖通信渠道,爱丽丝和鲍勃可以利用每天通过互联网上传和共享数亿张照片这一事实。爱丽丝可以直接在图像中的预定位置隐藏她的消息,而不必直接进行通信,而鲍勃可以访问此消息。从夏娃的角度来看,两者之间现在没有直接联系。
单个图像由数百万个像素组成。尽管存在许多格式,但是像素最简单地由一组介于0到255之间的三个数字表示,每个数字分别代表该像素的红色,蓝色和绿色值。使用这种红-绿-蓝方案,我们可以代表RGB颜色模型中的任何颜色。
数字文本(例如图像)在内部也用数字表示,因此文本文件和图像文件之间的差异并不像您想象的那么大。任何数字数据都可以表示为二进制字符串,一串1和0,我们可以对图像进行微小的修改以在其中编码二进制字符串。例如,请考虑以下内容:
这是具有三个像素的图像的表示形式:一个红色,一个绿色和一个蓝色。如果将其编码为图像并在图像查看器中打开,我们将看到三个像素的图像,但是如果我们使用Python读取此数据,则它只是一个元组列表,每个元组包含三个整数。
我们还可以查看组成每个像素的每个值,并计算它是奇数还是偶数。我们可以将奇数编码为1,将偶数编码为0。这将为我们提供二进制字符串" 100 010 001"。 (因为255个值是奇数,0个是偶数)。
在任何图像查看器中,图像看起来几乎都是相同的(我们刚刚从某些值中添加或减去了少量的颜色),但是使用我们的奇/偶方法,二进制字符串看起来将完全不同:" 011111100"。
使用此技术但将其扩展到整个图像(数百万像素),我们可以在任何图像中隐藏大量文本数据。
如果您认真考虑将消息尽可能保密,则希望在完全控制的脱机计算机上执行所有这些步骤。不过,作为学习练习,我们将在repl.it上设置项目。如果您没有帐户,请导航到他们的网站并注册一个帐户。
创建一个新项目,选择" Python"作为语言,并为您的项目命名。
我们需要构建的第一部分是将任何文本消息编码为二进制字符串的函数。
import base64def encode_message_as_bytestring():b64 = .encode(" utf8")bytes_ = base64.encodebytes(b64)bytestring ="" .jo [" {:08b}& #34;。 ()for bytes_])bytestring
这首先将我们的文本编码为base64,然后编码为二进制字符串。您可以添加一些打印语句,以查看如何在不同的步骤中转换消息,如下所示。
base64步骤不是严格必需的,但是它很有用,因为任何文件或数据都可以编码为base64。这使我们的项目可以扩展到将来的扩展,例如在图像文件中隐藏其他类型的文件,而不仅仅是文本字符串。
我们假设我们的信息将始终“适合”您的信息。在我们的形象。我们可以将每个像素容纳三个二进制数字(每个RGB值一个),因此,我们得到的二进制字符串应短于图像中像素的数目乘以3。
我们还需要知道消息何时结束。该消息将仅在图像文件的开头进行编码,但是如果我们不知道该消息有多长,我们将继续查看正常像素并将其编码为文本数据。让我们添加一个字符串的结尾。在消息末尾定界:这应该是偶然出现在我们实际消息中间的内容。我们将使用'!ENDOFMESSAGE!'的二进制表示形式为了这。
导入base64ENDOFMESSAGE =" 0100100101010101010101100100111101010010010001010011100101000111010101000101010101010110010101000101010100110000010001100100100001010010010010010010001000100101101" def encode_message_as_bytestring(message):b64 = 34。 。 join([" {:08b}" .format(x)for x in bytes_]])字节串+ = ENDOFMESSAGE返回字节串
在某处找到PNG图片-您是自己拍摄的,还是来自unsplash之类的网站。如果只有.jpg文件,则可以使用任何在线JPG到PNG转换器。
通过单击repl侧栏中的三点菜单(在文件窗格的右上角到左侧),然后选择上载文件,或通过简单地将文件拖放到文件窗格中,来上载PNG文件。
我们将编写一个从该图像文件中提取原始像素数据的函数。将导入添加到文件顶部。
我们主要对第三项" rows"感兴趣,它是一个迭代器,它逐行包含图像的所有像素。如果您不熟悉Python生成器,请参阅本指南,但它们本质上是内存有效的列表。
现在我们已经准备好图像的编码消息和像素,可以将它们组合起来以形成我们的秘密编码图像。
将以下函数添加到main.py文件的底部。此函数接收先前函数的输出(我们的原始像素和我们的消息编码为二进制字符串),并将它们组合在一起。
def(像素,字节串):'''修改像素以对字节串中的内容进行编码enc_pixels = [] string_i = 0(以像素为单位):enc_row = [] for i,char枚举(行):如果string_i> = len(bytestring):像素= row [i]否则:if row [i]% 2!= int(bytestring [string_i]):如果row [i] == 0:像素= 1 else:像素= row [i]-1 else:像素= row [i] enc_row.append(pixel)string_i + = 1 enc_pixels.append(enc_row)返回enc_pixels
这是我们项目中最复杂的部分,但是大多数代码都可以用来处理极端情况。重要的见解是,我们要控制每个像素是一个奇数值(在我们的二进制字符串中表示1)还是偶数(表示0)。偶然地,一半的像素值将已经具有正确的值。
我们只是简单地遍历二进制字符串和像素以及“凸点”。每个不正确的值都不能以1校正。也就是说,如果需要将其从奇数更改为偶数,反之亦然,则从值中减去1。我们不希望有任何负数,因此,如果需要更改0个值中的任何一个,则改为添加1。
现在,我们拥有了所有图像数据,包括编码后的消息,但它仍然只是像素列表。让我们添加一个将像素重新编译为PNG图像的函数。
上面的函数获取数组像素,并使用png模块将其写入到全新的.png文件中。
尝试使用这些功能,以确保您了解它们的工作原理。在编写一些包装代码以实际使用它们之前,我们将向后进行所有操作,以便我们还可以从以前编码的PNG文件中提取隐藏的消息。
首先,我们需要一个可以将二进制字符串转换回可读文本的函数。和以前一样,我们将通过base64获得更好的兼容性。将以下函数添加到main.py文件的底部。
def解码_message_from_bytestring(字节串):字节串=字节串.split(ENDOFMESSAGE)[0]消息= int(字节串,2)。到_bytes(len(bytestring)// 8,byteorder =' big')message = base64.decodebytes(message).decode(" utf8")返回消息
还记得我们如何在上面添加特殊的ENDOFMESSAGE分隔符吗?在这里,我们首先将字符串拆分为字符串,这样我们就不会在随机数据中寻找文本(来自图像未修改部分的像素),然后向后遍历编码管道:首先到达base64,然后再到达文本。
我们还需要一种从图像中提取字节串的方法。将以下函数添加到main.py中以执行此操作。
def encode_pixels(像素):bytestring = []对于像素行:对于c行:字节字符串。 append(str(c%2))字节串=''。 join(bytestring)消息=解码_消息_from_bytestring(bytestring)返回消息
再一次,这只是我们之前所做的相反。我们获取每个值的其余部分,以使每个奇数获得1,为每个偶数获得0,并将其保留在字符串中。然后,我们调用解码函数以获取纯文本。
这就是我们的编码和解码功能;接下来,我们将所有内容放到main()函数中。
在这一点上,我们可以创建一个带有UI的Web应用程序,以便人们将文本添加到他们的图像中。考虑到想要进行隐写术的人们可能不会信任其数据的Web应用程序,我们宁愿创建一个命令行应用程序,使人们可以在自己的计算机上运行该应用程序。
=""欢迎使用基本隐写术。请选择:1。将消息编码为图像2。将图像解码为消息q。退出"""
现在,让我们编写将所有内容组合在一起的main()函数。将以下内容添加到main.py文件的末尾。
def main():print(PROMPT)user_inp =""而user_inp不在(" 1&#34 ;、" 2&#34 ;、" q")中:user_inp = input("您的选择:")如果user_inp ==" 1&#34 ;: in_image =输入("请输入现有PNG图像的文件名:")in_message =输入("请输入要编码的消息:&# 34;)print(" -ENCODING-")像素=获取_pixels_from_image(in_image)字节字符串=编码_message_as_bytestring(in_message)epixels =编码_pixels_with_message(像素,字节字符串)写入_pixels_to_image(epixels,in_image + ; -enc.png")elif user_inp ==" 2&#34 ;: in_image = input("请输入现有PNG图像的文件名:")print(&#34 ; -DECODING-")像素=如果__name__ ==" __ main __"则获得_pixels_from_image(in_image)print(解码_pixels(pixel)):main()
上面的main()函数为用户创建了与程序交互的提示流。取决于用户的输入,程序将调用相关功能以对消息进行编码或解码。我们还为用户提供了一个q来关闭程序。
如果您一直遵循,您将有自己的代表进行扩展;如果不是,您可以派遣我们的代表并从那里进行工作或在下面进行测试。