这学期上的信息隐藏课的实验,这次做的是LSB算法。因为写实验报告是用markdown写然后转pdf提交的,所以就记录下。
前言
感觉这个LSB写的有点辣鸡,凑合看吧(233)
本来还想把实验报告改的通顺一点再放上来,但是因为懒,所以直接粘贴过来了。。
实验要求
实现LSB信息隐藏算法,使其能够隐藏信息,并且能够还原,然后计算原图和隐藏了信息的图的MSE、PSNR
实验环境
采用python进行编程
其中python版本是: 3.6
用到的库及说明如下:1
2
3
4
5
6
7
8
9
10from PIL import Image # 为了对载体图片进行像素操作
import base64 # 实验思路是对文字和图片进行不同方式处理
import qrcode # 如果要隐藏的是图片的话,把图片base64编码后再隐藏
# 如果要隐藏的是文字的话,先用qrcode生成一张二维码,
# 把文字隐藏在二维码里,然后再把二维码图片base64后隐藏
import numpy as np #为了计算PSNR、MSE
import math
import cv2
实验思路
关于这个实验我的设计思路是。
先编写用于读取要隐藏的文件的函数get_data(),相关代码如下。简单解释下,先是根据传入的文件名判断文件的后缀,如果是
txt
文件则使用generate_qrcode()函数生成包含txt内容的二维码,然后再把二维码图片进行base64转换隐藏。如果是png、jpg、gif
文件的话,先调用img_to_b64()函数转为base64编码后,然后再进行隐藏。进行完base64转换后,在要隐藏的数据后面加上\r\n\r\n + 后缀
,以此作为结束符,也方便还原,然后把内容从字节流转为8位比特流。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39# 获取需要隐藏的文件信息
def get_data(path):
data = ''
# 获取文件类型,根据类型不同操作
ext = path[path.find('.') + 1:]
if ext.lower() == 'txt':
img_path = generate_qrcode(path)
# 生成二维码后的文件扩展为png
ext = 'png'.upper()
ext = '\r\n\r\n' + ext
# 藏的时候要把图片扩展名藏进去,方便还原
b64data = img_to_b64(img_path) + ext
if ext.lower() in ['png','jpg','gif']:
ext = '\r\n\r\n' + ext.upper()
b64data = img_to_b64(path) + ext
# 把信息转为比特流
for i in range(len(b64data)):
data += bin(ord(b64data[i])).replace('0b','').zfill(8) #zfill返回指 定长度字符串,不足填0
return data
# 把图片转为base64,返回base64字串
def img_to_b64(path):
f = open(path,'rb')
s = f.read()
f.close()
res = base64.b64encode(s).decode()
return res
# 把txt内容转为二维码,返回生成的二维码路径
def generate_qrcode(path):
f = open(path,'rb')
text = f.read()
f.close()
img = qrcode.make(text)
save_path = path[:path.find('.')] + '.png'
img.save(save_path)
return save_path编写lsb加密函数,这里用PIL库里的Image包文件,先判断要隐藏的信息是否超过载体最低一位的容量,如果超过即输出提示信息
载体装不下,换个大点的吧
,然后退出。若没有超过容量的话,就遍历载体图片的像素点,利用模2取出最后一位,然后替换成我们要隐藏的信息。重要操作的话,下面代码都附上了注释,这里就不多叙述了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42# old 载体,path 隐藏信息,new 新生成的图片
def lsb(old,path,new):
im = Image.open(old)
width,height = im.size[0],im.size[1]
count = 0
data = get_data(path)
if len(data) > width * height *3:
print("载体装不下,换个大点的吧")
exit()
data_len = len(data)
for h in range(height):
for w in range(0,width):
pixel = im.getpixel((w,h)) #获取像素
a = pixel[0]
b = pixel[1]
c = pixel[2]
# 每次循环前判断是否藏完
if count == data_len:
break
a = a - mod(a,2) + int(data[count]) # 先把最低一位减了再藏
count += 1
if count == data_len:
im.putpixel((w,h),(a,b,c))
break
b = b - mod(b,2) + int(data[count])
count += 1
if count == data_len:
im.putpixel((w,h),(a,b,c))
break
c = c - mod(c,2) + int(data[count])
count += 1
if count == data_len:
im.putpixel((w,h),(a,b,c))
break
if count % 3 == 0: #一个像素藏完,putpixel一下
im.putpixel((w,h),(a,b,c))
im.save(new)然后编写解密函数,lsb_decode(),代码如下,这里我是直接把图像的最后一位先全部提出来,然后根据上面加密函数里最后放入的
\r\n\r\n
标记判断是否结束,然后找出扩展名,将提取出的数据base64解码后存到文件里,这里生成的文件名用lsb_decode.
+找出的扩展名。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24def lsb_decode(path):
im = Image.open(path)
width,height = im.size[0],im.size[1]
data = ''
for h in range(height):
for w in range(width):
pixel = im.getpixel((w,h)) #获取像素
for p in pixel[:3]:
data += str(mod(p,2))
temp_data = ''
for i in range(0,len(data),8):
temp = int(data[i:i+8],2) #转成十进制
temp_data += chr(temp)
# 藏的信息结束位置
end_pos = temp_data.find('\r\n\r\n')
msg_b64 = temp_data[:end_pos]
msg_ext = temp_data[end_pos+4:end_pos+7]
msg = base64.b64decode(msg_b64)
msg_name = "lsb_decode." + msg_ext.lower()
f = open(msg_name,'wb')
f.write(msg)
f.close()然后编写计算MSE、PSNR的函数,这里代码就是利用PSNR和MSE的公式计算,不多讲。
1
2
3
4
5
6
7
8
9
10def PSNR(old,new):
import numpy as np
import math
import cv2
img1,img2 = cv2.imread(old),cv2.imread(new)
mse = np.mean((img1 - img2) ** 2 )
if mse < 1.0e-10:
return 100
psnr = 10 * math.log10(255.0**2/mse)
print("MSE为:{0}\nPSNR为:{1}".format(mse,psnr))
实验测试
测试隐藏txt文件,要隐藏的
1.txt
文件内容为易涛LSB实验测试
,使用下面代码测试。预期结果应该是,产生1.png
(1.txt
的二维码文件),隐藏有1.png
base64编码的lsb_encode_txt.bmp
文件,和解码还原后的lsb_decode.png
二维码1
2
3
4
5
6
7if __name__ == "__main__":
data_path = r"1.txt"
image_path = r"mitu.bmp"
new_path = r"lsb_encode_txt.bmp"
lsb(image_path,data_path,new_path)
lsb_decode("lsb_encode_txt.bmp")
PSNR(image_path,new_path)下面截图是未执行代码前的截图
执行一下代码,代码执行结果如下,可以看到计算出了MSE和PSNR
而我们的目录也多出来了系列文件,可以扫描下面截图的两个二维码从而获取隐藏的信息易涛LSB实验测试
测试隐藏图片文件,隐藏的文件名为
shell.gif
,用下面代码测试。预期结果应该是,生成隐藏有shell.gif
base64编码的lsb_encode_img.bmp
文件,和解码还原后的lsb_decode.gif
文件1
2
3
4
5
6
7if __name__ == "__main__":
data_path = r"shell.gif"
image_path = r"mitu.bmp"
new_path = r"lsb_encode_img.bmp"
lsb(image_path,data_path,new_path)
lsb_decode("lsb_encode_img.bmp")
PSNR(image_path,new_path)下面是未执行代码前的文件夹目录
代码执行结果截图如下,可以看到计算的MSE和PSNR值,PSNR值比上面小了一些,可能是隐藏的shell.gif
的大小比二维码大导致。
执行完后的目录截图如下。可以看到,和预期结果一致。
实验源码
整个实验的源码lsb.py
如下,实验的代码和测试用到的图片也会以压缩包附件形式上传。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150#!/usr/bin/env python
# coding=UTF-8
'''
@Author: 易涛
@LastEditors: 易涛
@Description: LSB隐写算法
@Date: 2019-04-30 13:41:37
@LastEditTime: 2019-05-08 16:43:23
'''
from PIL import Image
import base64
import qrcode
# 获取需要隐藏的文件信息
def get_data(path):
data = ''
# 获取文件类型,根据类型不同操作
ext = path[path.find('.') + 1:]
if ext.lower() == 'txt':
img_path = generate_qrcode(path)
# 生成二维码后的文件扩展为png
ext = 'png'.upper()
ext = '\r\n\r\n' + ext
# 藏的时候要把图片扩展名藏进去,方便还原
b64data = img_to_b64(img_path) + ext
if ext.lower() in ['png','jpg','gif']:
ext = '\r\n\r\n' + ext.upper()
b64data = img_to_b64(path) + ext
# 把信息转为比特流
for i in range(len(b64data)):
data += bin(ord(b64data[i])).replace('0b','').zfill(8) #zfill返回指定长度字符串,不足填0
return data
# 把图片转为base64,返回base64字串
def img_to_b64(path):
f = open(path,'rb')
s = f.read()
f.close()
res = base64.b64encode(s).decode()
return res
# 把txt内容转为二维码,返回生成的二维码路径
def generate_qrcode(path):
f = open(path,'rb')
text = f.read()
f.close()
img = qrcode.make(text)
save_path = path[:path.find('.')] + '.png'
img.save(save_path)
return save_path
def mod(x,y):
return x%y
# old 载体,path 隐藏信息,new 新生成的图片
def lsb(old,path,new):
im = Image.open(old)
width,height = im.size[0],im.size[1]
count = 0
data = get_data(path)
if len(data) > width * height *3:
print("载体装不下,换个大点的吧")
exit()
data_len = len(data)
for h in range(height):
for w in range(0,width):
pixel = im.getpixel((w,h)) #获取像素
a = pixel[0]
b = pixel[1]
c = pixel[2]
# 每次循环前判断是否藏完
if count == data_len:
break
a = a - mod(a,2) + int(data[count]) # 先把最低一位减了再藏
count += 1
if count == data_len:
im.putpixel((w,h),(a,b,c))
break
b = b - mod(b,2) + int(data[count])
count += 1
if count == data_len:
im.putpixel((w,h),(a,b,c))
break
c = c - mod(c,2) + int(data[count])
count += 1
if count == data_len:
im.putpixel((w,h),(a,b,c))
break
if count % 3 == 0: #一个像素藏完,putpixel一下
im.putpixel((w,h),(a,b,c))
im.save(new)
def lsb_decode(path):
im = Image.open(path)
width,height = im.size[0],im.size[1]
data = ''
for h in range(height):
for w in range(width):
pixel = im.getpixel((w,h)) #获取像素
for p in pixel[:3]:
data += str(mod(p,2))
temp_data = ''
for i in range(0,len(data),8):
temp = int(data[i:i+8],2) #转成十进制
temp_data += chr(temp)
# 藏的信息结束位置
end_pos = temp_data.find('\r\n\r\n')
msg_b64 = temp_data[:end_pos]
msg_ext = temp_data[end_pos+4:end_pos+7]
msg = base64.b64decode(msg_b64)
msg_name = "lsb_decode." + msg_ext.lower()
f = open(msg_name,'wb')
f.write(msg)
f.close()
def PSNR(old,new):
# target:目标图像 ref:参考图像 scale:尺寸大小
# assume RGB image
import numpy as np
import math
import cv2
img1,img2 = cv2.imread(old),cv2.imread(new)
mse = np.mean((img1 - img2) ** 2 )
if mse < 1.0e-10:
return 100
psnr = 10 * math.log10(255.0**2/mse)
print("MSE为:{0}\nPSNR为:{1}".format(mse,psnr))
if __name__ == "__main__":
data_path = r"shell.gif" # 自己选择
image_path = r"mitu.bmp"
new_path = r"lsb_encode_img.bmp"
lsb(image_path,data_path,new_path)
lsb_decode("lsb_encode_img.bmp")
PSNR(image_path,new_path)