python实现简单的图片隐写术

来源:互联网 发布:融资租赁软件市场 编辑:程序博客网 时间:2024/06/11 09:52

本文转载自Python3图片隐写术
载体文件相对隐秘文件的大小越大,隐藏后者就越加容易。因为这个原因,数字图像在因特网和其他传媒上被广泛用于隐藏消息。例如一个24位的位图中的每个像素的三个颜色分量(红,绿和蓝)各使用8个比特来表示,如果我们只考虑蓝色的话,就有2^8种不同的数值来表示深浅不同的蓝色。而像11111111和11111110这两个值所表示的蓝色,人眼几乎无法区分。因此,这个最低有效位就可以用来存储颜色之外的信息,而且在某种程度上几乎是检测不到的。如果对红色和绿色进行同样的操作,就可以在差不多三个像素中存储一个字节的信息。本实验便是利用RGBA(RGB+Alpha透明度)的最低有效位(Least Significant Bit)来隐藏文字。
1.开发环境
Python3+Pillow库
2.实验过程
开始编写我们的代码。

def encodeDataInImage(image, data):    evenImage = makeImageEven(image)     binary = ''.join(map(constLenBin,bytearray(data, 'utf-8')))    if len(binary) > len(image.getdata()) * 4:         raise Exception("Error: Can't encode more than " + len(evenImage.getdata()) * 4 + " bits in this image. ")    encodedPixels = [(r+int(binary[index*4+0]),g+int(binary[index*4+1]),b+int(binary[index*4+2]),t+int(binary[index*4+3])) if index*4 < len(binary) else (r,g,b,t) for index,(r,g,b,t) in enumerate(list(evenImage.getdata()))]     encodedImage = Image.new(evenImage.mode, evenImage.size)     encodedImage.putdata(encodedPixels)      return encodedImage

将隐藏信息编码到图片中的encodeDataInImage函数有两个参数,分别是用作载体的图片对象和需要被隐藏的字符串。首先调用makeImageEven函数获得最低有效位为0的图片副本,然后调用constLenBin函数将需要被隐藏的字符串转换成二进制字符串。如果不能编码全部数据,则抛出异常。接下来将binary中的二进制字符串信息编码进像素里,创建新图片以存放编码后的像素,最后在新图片中添加编码后的数据。
bytearray函数将字符串转换为整数值序列。
这里写图片描述
makeImageEven函数通过一个移位操作把所有值改成偶数。

def makeImageEven(image):    pixels = list(image.getdata())     evenPixels = [(r>>1<<1,g>>1<<1,b>>1<<1,t>>1<<1) for [r,g,b,t] in pixels]     evenImage = Image.new(image.mode, image.size)      evenImage.putdata(evenPixels)      return evenImage

map(constLenBin,bytearray(data,'utf-8'))对数值序列中的每一个值应用constLenBin函数,去掉bin返回的二进制字符串中的0b,并在左边补足0直到字符串长度为8,从而将十进制数值序列转换为二进制字符串序列。

def constLenBin(int):    binary = "0"*(8-(len(bin(int))-2))+bin(int).replace('0b','')     return binary

decodeImage函数返回图片解码后的隐藏文字,其接受一个图片对象参数。找到数据截止处所用的字符串0000000000000000很有意思,它的长度为16,而不是直觉上的8,因为两个包含数据的字节的接触部分可能有8个0。

def decodeImage(image):    pixels = list(image.getdata())     binary = ''.join([str(int(r>>1<<1!=r))+str(int(g>>1<<1!=g))+str(int(b>>1<<1!=b))+str(int(t>>1<<1!=t)) for (r,g,b,t) in pixels])     locationDoubleNull = binary.find('0000000000000000')    endIndex = locationDoubleNull+(8-(locationDoubleNull % 8)) if locationDoubleNull%8 != 0 else locationDoubleNull    data = binaryToString(binary[0:endIndex])    return data

binaryToString函数将提取出来的二进制字符串转换为隐藏的文本。

def binaryToString(binary):    index = 0    string = []    rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i>1 else '') if x else ''    fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)    while index + 1 < len(binary):        chartype = binary[index:].index('0')         length = chartype*8 if chartype else 8        string.append(chr(int(fun(binary[index:index+length],chartype),2)))        index += length    return ''.join(string)

要看明白这个,必须要先搞懂UTF-8编码的方式。UTF-8是unicode的一种变长度的编码表达方式,也就是说一个字符串中,不同的字符所占的字节数不一定相同。如果我们要支持中文的话,这就给我们的工作带来了一点复杂度。
这里写图片描述
在上图中,只有x所在的位置(也即是字节中第一个0之后的数据)存储的是真正的字符数据,因此我们使用下面两个匿名函数来提取出这些数据。

    rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i>1 else '') if x else ''    fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)

fun接收两个参数,第一个参数为一个字符的二进制字符串,这个二进制字符串可能有不同的长度(8、16、24……48);第二个参数为这个字符占多少个字节。lambda x, i: x[i+1:8] + rec(x[8:], i-1)中x[i+1:8]获得第一个字节的数据,然后调用rec,以递归的方式提取后面字节中的数据。字符的字节数据中,第一个字节开头1的数目便是字符所占的字节数。
string.append(chr(int(fun(binary[index:index+length],chartype),2)))这一行中用到的函数int和chr的作用如下。
chr:接收一个参数,参数为int值,返回值这个int值的字符。
int:接收两个参数,第一个参数为数字字符串,第二个参数为这个数字字符串代表的数字的进制。
完整的代码如下。

from PIL import Imagedef makeImageEven(image):    pixels = list(image.getdata())     evenPixels = [(r>>1<<1,g>>1<<1,b>>1<<1,t>>1<<1) for [r,g,b,t] in pixels]     evenImage = Image.new(image.mode, image.size)      evenImage.putdata(evenPixels)      return evenImagedef constLenBin(int):    binary = "0"*(8-(len(bin(int))-2))+bin(int).replace('0b','')     return binarydef encodeDataInImage(image, data):    evenImage = makeImageEven(image)     binary = ''.join(map(constLenBin,bytearray(data,'utf-8')))    if len(binary) > len(image.getdata()) * 4:         raise Exception("Error: Can't encode more than " + len(evenImage.getdata()) * 4 + " bits in this image. ")    encodedPixels = [(r+int(binary[index*4+0]),g+int(binary[index*4+1]),b+int(binary[index*4+2]),t+int(binary[index*4+3])) if index*4 < len(binary) else (r,g,b,t) for index,(r,g,b,t) in enumerate(list(evenImage.getdata()))]     encodedImage = Image.new(evenImage.mode, evenImage.size)     encodedImage.putdata(encodedPixels)      return encodedImagedef binaryToString(binary):    index = 0    string = []    rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i>1 else '') if x else ''    fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)    while index + 1 < len(binary):        chartype = binary[index:].index('0')         length = chartype*8 if chartype else 8        string.append(chr(int(fun(binary[index:index+length],chartype),2)))        index += length    return ''.join(string)def decodeImage(image):    pixels = list(image.getdata())     binary = ''.join([str(int(r>>1<<1!=r))+str(int(g>>1<<1!=g))+str(int(b>>1<<1!=b))+str(int(t>>1<<1!=t)) for (r,g,b,t) in pixels])     locationDoubleNull = binary.find('0000000000000000')    endIndex = locationDoubleNull+(8-(locationDoubleNull % 8)) if locationDoubleNull%8 != 0 else locationDoubleNull    data = binaryToString(binary[0:endIndex])    return dataencodeDataInImage(Image.open("coffee.png"), 'hello word你好!').save('encodeImage.png')print(decodeImage(Image.open("encodeImage.png")))
0 0
原创粉丝点击