概述
当谈到信息安全和数据保护时,图像加密、解密以及数字水印处理在保护隐私和确保数据安全方面扮演着至关重要的角色。随着数字化时代的快速发展,图像数据的安全性和隐私保护成为了迫切需要解决的问题。Python作为一种功能强大的编程语言,为我们提供了一种高效且灵活的方式来实现图像加密解密与数字水印处理。
图像加密与解密
图像加密是一种保护图像数据不被未授权访问的技术。它通过将图像的像素值或其他关键信息转化为一种不易被识别和解析的形式,确保只有持有正确密钥的人才能解密并访问原始图像。
在Python中,我们可以利用各种加密算法来实现图像加密。例如,可以使用AES(高级加密标准)算法对图像数据进行加密,通过Python的
cryptography
库可以轻松实现。另外,还有一些基于混沌理论的加密算法,如Logistic映射等,也可以用于图像加密。解密过程则是加密的逆过程,它使用相同的密钥和算法将加密后的图像数据还原为原始图像。在Python中,我们可以编写相应的解密函数或调用加密库中的解密功能来实现这一过程。
数字水印处理
数字水印是一种将特定信息嵌入到数字内容(如图像、音频或视频)中的技术。水印信息可以是文本、图像或二进制数据,用于标识版权、身份验证或追踪来源。通过在水印处理过程中采用加密技术,可以进一步增强水印的安全性。
在Python中,我们可以使用图像处理库(如PIL/Pillow)来实现数字水印的嵌入和检测。首先,需要选择一个合适的水印算法,并根据算法的要求对原始图像和水印信息进行预处理。然后,将水印信息嵌入到原始图像的像素值或其他关键位置中。最后,通过检测算法从可能包含水印的图像中提取水印信息,并验证其有效性。
原理介绍
1.图像加密与解密
图像加密通常涉及将图像的像素值或某些特征进行变换,使得未授权的用户无法直接解读原始图像的内容。解密则是加密的逆过程,用于恢复原始图像。
加密算法示例:
- AES(高级加密标准):虽然AES通常用于文本加密,但也可以将其应用于图像的字节表示。你需要将图像转换为字节流,然后应用AES算法进行加密。
- 混沌加密:利用混沌理论(如Logistic映射)来生成加密密钥,并基于这些密钥对图像进行加密。
Python库:
cryptography
:提供了多种加密算法的实现,包括AES。PIL
(Python Imaging Library,也称为Pillow
):用于处理图像文件,如读取、写入和转换图像格式。
2. 数字水印处理
数字水印是一种嵌入在数字内容(如图像、音频或视频)中的不可见或几乎不可见的标识符或信息。水印通常用于版权保护、身份验证或追踪来源。
水印处理步骤:
- 水印嵌入:选择或创建水印信息(如文本、图像或二进制数据),并将其嵌入到原始图像的某些部分(如像素的最低有效位)。
- 水印检测:从可能包含水印的图像中提取水印信息,并验证其有效性。
Python库:
PIL
(或Pillow
):用于处理图像。- 自定义算法:你可能需要编写自定义的算法来处理水印的嵌入和检测。
核心逻辑
实现窗口可视化
这里我们使用python自带的tkinter标准库实现图形用户界面(GUI)应用程序,其提供了一组工具和组件,使开发者能够创建窗口、按钮、文本框、标签等各种 GUI 元素,并通过编写代码来处理用户的输入和操作。
首先我们先导入tkinter库及其对应要使用的函数:
from tkinter import *
# 导入tkinter中的对话框函数messagebox并将其重命名为 mbox
import tkinter.messagebox as mbox
通过主函数main()创建窗口,并设计其对应的样式:
def main():
# 创建主窗体布局
root = Tk()
# 设置窗口大小为 300x200 像素,并将其放置在屏幕位置 (600, 200)
root.geometry('500x300+600+200')
# 设置标题
root.title('图像加密及数字水印处理系统')
"""
创建一个标签组件
参数: root表示该组件父容器即窗口,text参数表示显示文本内容,bg为标签背景颜色
pack() 方法将标签添加到父容器中,fill=X 表示填充方向为水平方向(X 轴方向)
"""
Label(root, text='功能选择', bg='#008c8c').pack(fill=X)
"""
创建一个按钮组件
参数: root是该组件的父容器,text参数表示按钮上显示的文本内容,font参数设置按钮文本的字体和大小,command参数设置按钮的回调函数
这里使用一个lambda表达式来定义回调函数。表达式实际上是一个匿名函数,当用户点击按钮时,会调用该函数,然后函数会调用goto()函数并将参数传递给它。
"""
接下来继续在main()函数下给窗口添加内容,这里添加四个按钮,并给每个按钮设置回调函数:
# 进入子窗体回调函数
def goto(num):
# 关闭主窗口
root.destroy()
# 使用字典来映射数字和对应的窗口函数,然后在 goto 函数中进行查找和调用,如果找到则调用该函数,否则不执行任何操作。
windows = {
1: One,
2: Two,
3: Three
}
func = windows.get(num, lambda: None)
if func:
func()
# 退出系统
def LogDown():
if mbox.askokcancel("退出", "确认退出系统吗?"):
root.destroy()
# 设置窗口布局
btn1 = Button(root, text="数字图像加密", font=('Arial', 12), command=lambda: goto(1))
# pack()方法将按钮组件添加到父容器中。参数用于设置垂直方向上的外边距(即上下边距),其值为 15。
btn1.pack(pady=15)
btn2 = Button(root, text="数字图像解密", font=('Arial', 12), command=lambda: goto(2))
btn2.pack(pady=15)
btn3 = Button(root, text="数字水印处理", font=('Arial', 12), command=lambda: goto(3))
btn3.pack(pady=15)
btn4 = Button(root, text="退出系统", font=('Arial', 12), command=LogDown)
btn4.pack(pady=15)
# 监听并处理用户的事件,直到程序被显式关闭或者窗口被销毁, 只有当主事件循环结束(即窗口被关闭或销毁)时,才会继续执行 mainloop() 后面的代码。
root.mainloop()
接下来给字典映射的函数名称放置在和main()函数平级的位置,最后调用主函数
# 第一个窗口
def One():
print(1)
# 第二个窗口
def Two():
print(1)
# 第三个窗口
def Three():
print(1)
# 执行 main() 函数
if __name__ == '__main__':
main()
最终执行的效果如下:
感觉画面有点小丑,加个图片试试吧, 需要导入 ImageTk 和 Image 类,它们位于 PIL(Python Imaging Library)库中,终端执行如下命令进行安装:
pip install pillow
然后设置背景图片,根据自己设备的图片,调整按钮的上边距内容:
# 加载背景图片
bg_image = Image.open("bg.jpg")
bg_image = bg_image.resize((800, 500), Image.BILINEAR) # 调整图片大小
bg_photo = ImageTk.PhotoImage(bg_image)
# 创建一个标签组件作为背景
bg_label = Label(root, image=bg_photo)
bg_label.place(x=0, y=0, relwidth=1, relheight=1)
画面如下(效果还行,暂时先这样吧,后期有新功能再接着改):
数字图像加密
数字图像加密按钮,当我们点击之后也是呈现一个窗口,这里撰写两种对图像进行加密的方式,ok接下来我们首先实现基本窗口的设置:
窗口布局设置
图像加密窗口的布局样式与主窗口的布局样式一致,这里就不再赘述了,具体的代码如下:
# 第一个窗口
def One():
# 函数内部使用全局变量encryption
global encryption
# 将全局变量encryption的值设置为True,以表示已经进行了加密操作。
encryption = True
# 创建子窗口布局
root1 = Tk()
# 设置窗口大小为 300x200 像素,并将其放置在屏幕位置 (600, 200)
root1.geometry('800x500+600+200')
# 设置标题
root1.title('数字图像加密')
Label(root1, text='功能选择', bg='#008c8c').pack(fill=X)
# 进入当前窗口的子窗体回调函数
def goto(num):
# 关闭主窗口
root1.destroy()
# 使用字典来映射数字和对应的窗口函数,然后在 goto 函数中进行查找和调用,如果找到则调用该函数,否则不执行任何操作。
windows = {
1: One_one,
2: One_two,
}
func = windows.get(num, lambda: None)
if func:
func()
# 设置窗口布局
btn1 = Button(root1, text="返回主窗体", command=lambda: gotoMain(root1))
btn1.pack(side="bottom", pady=10)
btn2 = Button(root1, text="基于混沌Logistic的图像加密", font=('Arial', 12), command=lambda: goto(1))
btn2.pack(pady=(115, 15))
btn3 = Button(root1, text="基于三重DES的图像加密", font=('Arial', 12), command=lambda: goto(2))
btn3.pack(pady=15)
btn4 = Button(root1, text="退出系统", font=('Arial', 12), command=lambda: LogDown(root1))
btn4.pack(pady=15)
root1.mainloop()
从上面的代码中,我们设置了一个全局变量用于表达图像是否处在加密状态中:
# 定义全局变量
encryption = False # 表示未加密状态
最终呈现的结果如下:
基于混沌Logistic的图像加密
当我们点击该加密算法的按钮之后,通过回调函数进入到该加密算法界面:
后接下来我们开始设置进入到该图像加密的UI界面的设置,代码如下:
def One_one():
# 页面布局
root1_1 = Tk()
root1_1.geometry('800x500+600+200')
root1_1.title('数字图像加密')
Label(root1_1, text='基于混沌Logistic的图像加密', bg='#008c8c').pack(fill=X)
# 回调函数
def goOne():
root1_1.destroy()
One()
# 设置窗口布局
btn1 = Button(root1_1, text="返回上一窗体", command=goOne)
btn1.pack(side="bottom", pady=10)
btn2 = Button(root1_1, text="选择图像", font=('Arial', 12), command=Open_img)
btn2.pack(pady=(115, 15))
btn3 = Button(root1_1, text="加密图像", font=('Arial', 12), command=Logistical)
btn3.pack(pady=15)
btn4 = Button(root1_1, text="另存图像", font=('Arial', 12), command=SaveEncryptionImg)
btn4.pack(pady=15)
btn5 = Button(root1_1, text="重置图像", font=('Arial', 12), command=ResetEncryptionImg)
btn5.pack(pady=15)
root1_1.mainloop()
呈现的界面如下:
接下来我们开始写选择图像的回调函数进行书写,这里需要先下载opencv-python库,对于opencv-python库,它使用PEP 517来构建wheels,但是可能缺少一些必要的编译工具,解决此问题的方法是升级pip到最新版本,并确保已安装所有必要的编译工具。建议您也将pip升级到最新版本,可以通过运行以下命令来升级:
python -m pip install --upgrade pip
opencv-python这个库内存比较大,建议临时换源来进行安装
pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple/
具体代码如下:
# 可以进行图像读取、显示、保存、转换、滤波、特征提取等操作。
import cv2
# 可以进行矩阵运算、数组操作、数值计算等,常用于图像数据的处理和分析。
import numpy as np
# 提供了丰富的图像读取、写入、裁剪、旋转、缩放等功能。
from PIL import ImageTk, Image
global_img = "" # 表示文件路径
panelA = None # 也是一个用于显示图像的容器,可以将其添加到tkinter窗口中以显示图像。
panelB = None # 也是一个用于显示图像的容器,可以将其添加到tkinter窗口中以显示图像。
# 定义图像处理的相关信息的字典
encryption_img = {
# 加密
"encryption": np.array([]), "EncryptionImg": np.array([]), "ImgPath": "", "width": None, "height": None
}
decrypt_img = {
"decrypt": None, "DecryptionImg": None, "ImgPath": "", "width": None, "height": None} # 解密
# 上传图像
def Open_img():
global global_img, panelA
# 让用户选择一个文件,并返回所选文件的路径,并赋值给 global_img
global_img = filedialog.askopenfilename(title='open')
if global_img.endswith('jpg') or global_img.endswith('png') or global_img.endswith('bmp'):
img = cv2.imdecode(np.fromfile(global_img, dtype=np.uint8), 1)
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
size = img.shape
new_width = 320
scale_factor = new_width / rgb.shape[1]
new_height = int(rgb.shape[0] * scale_factor)
resize_image0 = cv2.resize(rgb, dsize=(new_width, new_height), interpolation=cv2.INTER_LINEAR)
resize_image1 = Image.fromarray(resize_image0)
img1 = ImageTk.PhotoImage(resize_image1)
panelA = Label(image=img1)
panelA.image = img1
panelA.place(x=10, y=(500 - panelA.winfo_reqheight()) / 2)
if encryption:
encryption_img["encryption"] = img
encryption_img["ImgPath"] = global_img
encryption_img["width"] = size[1]
encryption_img["height"] = size[0]
else:
print(1)
print("----------图像已打开----------")
else:
mbox.showinfo("错误", "上传的文件不是图片格式,请重新上传")
完成上传图像之后,接下来我们基于混沌Logistic的加密图像算法对图像进行加密:
def int2bin8(x): # 整型转8位二进制
return bin(x)[2:].zfill(8)
def int2bin16(x): # 整型转16位二进制
return bin(x)[2:].zfill(16)
# 基于混沌Logistic的加密图像
def Logistical():
global panelB
img = encryption_img["encryption"]
encryptionImg = np.zeros(img.shape, np.uint8)
x0 = 0.15624562
g0 = 164
j0 = 10
x = img.shape[0]
y = img.shape[1]
c = img.shape[2]
g0 = int2bin16(g0)
for s in range(x):
for n in range(y):
for z in range(c):
m = int2bin8(img[s][n][z]) # 像素值转八位二进制
ans = ""
for i in range(8):
ri = int(g0[-1]) # 取手摇密码机最后一位ri
qi = int(m[i]) ^ ri # 与像素值异或得qi
xi = 1 - math.sqrt(abs(2 * x0 - 1)) # f1(x)混沌迭代
if qi == 0: # 如果qi=0,则运用x0i+x1i=1;
xi = 1 - xi
x0 = xi # xi迭代
t = int(g0[0]) ^ int(g0[12]) ^ int(g0[15]) # 本源多项式x^15+x^3+1
g0 = str(t) + g0[0:-1] # gi迭代
ci = math.floor(xi * (2 ** j0)) % 2 # 非线性转换算子
ans += str(ci)
re = int(ans, 2)
encryptionImg[s][n][z] = re # 写入新图像
encryption_img["EncryptionImg"] = encryptionImg
rgb = cv2.cvtColor(encryptionImg, cv2.COLOR_BGR2RGB)
new_width = 320
scale_factor = new_width / rgb.shape[1]
new_height = int(rgb.shape[0] * scale_factor)
resize_image0 = cv2.resize(rgb, dsize=(new_width, new_height), interpolation=cv2.INTER_LINEAR)
arrayImage = Image.fromarray(np.uint8(resize_image0))
encryptionImg = ImageTk.PhotoImage(arrayImage)
panelB = Label(image=encryptionImg)
panelB.image = encryptionImg
panelB.place(x=(800 - panelB.winfo_reqwidth() - 10), y=(500 - panelB.winfo_reqheight()) / 2)
print("-----加密完成-----")
接下来就是将加密后的图片进行保存以及对图片进行重置两个函数的实现:
# 保存图片
def SaveEncryptionImg():
filePath = filedialog.askdirectory(title='open')
cv2.imencode(".jpg", encryption_img["EncryptionImg"])[1].tofile(filePath+'/EncryptionImg.jpg')
print("-----保存完成-----")
# 重置图片
def ResetEncryptionImg():
global panelA, panelB
encryption_img["encryption"] = np.array([])
encryption_img["EncryptionImg"] = np.array([])
if panelA:
panelA.place_forget()
if panelB:
panelB.place_forget()
print("-----重置完成-----")
最终呈现的结果如下:
基于三重DES的图像加密
当我们点击该加密算法的按钮之后,通过回调函数进入到该加密算法界面:
然后接下来我们开始设置进入到该图像加密的UI界面的设置,代码如下:
# 第1_2个子窗口
def One_two():
# 页面布局
root1_2 = Tk()
root1_2.geometry('800x500+600+200')
root1_2.title('数字图像加密')
Label(root1_2, text='基于三重DES的图像加密', bg='#008c8c').pack(fill=X)
# 回调函数
def goOne():
root1_2.destroy()
One()
# 设置窗口布局
btn1 = Button(root1_2, text="返回上一窗体", command=goOne)
btn1.pack(side="bottom", pady=10)
btn2 = Button(root1_2, text="选择图像", font=('Arial', 12), command=Open_img)
btn2.pack(pady=(115, 15))
btn3 = Button(root1_2, text="加密图像", font=('Arial', 12), command=DES3_encryption)
btn3.pack(pady=15)
btn4 = Button(root1_2, text="另存图像", font=('Arial', 12), command=SaveDES3Img)
btn4.pack(pady=15)
btn5 = Button(root1_2, text="重置图像", font=('Arial', 12), command=ResetEncryptionImg)
btn5.pack(pady=15)
root1_2.mainloop()
呈现的界面如下:
基于三重数据加密标准(Triple Data Encryption Standard,简称3DES)的图像加密是一种使用3DES算法对图像进行保护和加密的方法。
3DES是一种对称加密算法,它是对经典DES算法的改进和扩展。经典DES使用56位密钥对64位的数据块进行加密,但随着计算能力的提升,DES算法的安全性逐渐降低。为了增强安全性,3DES采用了多次DES加密操作,通常使用两个或三个不同的密钥,对数据进行多轮加密。
基于三重DES的图像加密过程如下:
密钥选择:选择两个或三个不同的密钥作为加密算法的输入。
分组操作:将待加密的图像数据分为固定大小的数据块(例如64位),每个数据块进行独立的加密操作。
加密操作:对每个数据块进行多轮的DES加密操作,通常采用加密-解密-加密(EDE)的模式。具体步骤如下:
使用第一个密钥进行DES加密;使用第二个密钥进行DES解密;(可选)使用第三个密钥再次进行DES加密。
合并结果:将加密后的数据块重新组合成加密图像。
基于三重DES的图像加密能够提供较高的安全性,通过多次加密操作增加了破解的难度。同时,由于每个数据块是独立加密的,因此即使出现部分数据损坏或丢失,也不会影响整个图像的解密。这使得基于三重DES的图像加密在保护敏感图像数据方面具有一定的优势。
具体实现代码如下:
"""
基于三重DES的加密图像与解密图像代码
"""
# 图片像素二进制值获取
def get_pixel(img):
img = cv2.imread(img)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_width = img.shape[1]
img_height = img.shape[0]
bin_data = ''
for i in range(img_width):
for j in range(img_height):
r, g, b = img[j, i] # 获取像素点的rgb值
bin_data += '{:08b}'.format(r)
bin_data += '{:08b}'.format(g)
bin_data += '{:08b}'.format(b)
return bin_data, img_width, img_height
# DES明文分组处理函数
def set_group(bin_data):
print("*** 正在进行明文分组 ***")
bin_data_list = []
data_length = len(bin_data)
i = 0
while data_length >= 64: # 按照
# print("正在划分第"+str(i)+"个明文分组 ",data_length)
bin_data_list.append(bin_data[:64])
data_length -= 64
bin_data = bin_data[64:]
i += 1
print("正在处理最后一个明文分组 ", data_length)
if data_length == 0: # 若恰好能够完全分组
return bin_data_list
else:
padding_length = 64 - data_length
padding_byte_size = padding_length # 8
# 填充采用 PKCS#5填充标准
padding_data = ('{:08b}'.format(padding_byte_size)) * padding_byte_size
last_group_data = bin_data + padding_data
bin_data_list.append(last_group_data) # 填加最后填充的内容
return bin_data_list
# 将明文分组信息储存在txt文件中,便于2组内加密中获取相应的明文数据
def save_group_data(bin_data_list):
f = open('group.txt', 'w')
for i in bin_data_list:
f.write(i + '\n')
f.close()
# 获取明文的全部分组
def get_group():
group_list = []
group_file_name = 'group.txt' # 明文分组文件名
f = open(group_file_name, 'r')
for line in f:
group_list.append(line.strip('\n'))
f.close()
return group_list
# 获取初始IV向量
def get_IV():
iv_seed = os.urandom(8) # 随机获取8个字符
iv_bin_str = ''
for i in iv_seed:
iv_bin_str += '{:08b}'.format(i)
return iv_bin_str
# 随机生成满足奇偶校验的64位密钥
def get_rand_key(p):
key_seed = os.urandom(8) # 随机获取8个字符
KEY_bin_str = ''
for i in key_seed:
binstr = '{:07b}'.format(i) # 将每个字符转成7位二进制,第8位用于表示奇偶校验位
xor = int(binstr[0]) ^ int(binstr[1]) ^ int(binstr[2]) ^ int(binstr[3]) ^ int(binstr[4]) ^ int(binstr[5]) ^ int(binstr[6])
for ix in range(7):
KEY_bin_str += str(binstr[ix])
KEY_bin_str += str(xor)
print('随机生成的密钥', p, '为: ', KEY_bin_str, ' ', len(KEY_bin_str), '位')
return KEY_bin_str
# 检测64位密钥的奇偶校验是否通过
def check_key(key_bin):
for i in range(0, 64, 8):
xor = int(key_bin[i]) ^ int(key_bin[i + 1]) ^ int(key_bin[i + 2]) ^ int(key_bin[i + 3]) ^ int(
key_bin[i + 4]) ^ int(key_bin[i + 5]) ^ int(key_bin[i + 6])
if xor != int(key_bin[i + 7]):
return False
return True
# 密钥置换选择 1
key_table1 = [
56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 62, 54,
46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3
]
# 密钥置换选择 2
key_table2 = [
13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, 51, 30, 36, 46, 54, 29,
39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31
]
# 初始置换 IP
IP = [
57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31,
23, 15, 7, 56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54,
46, 38, 30, 22, 14, 6
]
# 逆初始置换
IP_1 = [39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52,
20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25, 32, 0,
40, 8, 48, 16, 56, 24]
# 选择扩展运算 E
E = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8, 7, 8, 9, 10, 11, 12, 11, 12, 13, 14, 15, 16, 15, 16, 17, 18, 19, 20, 19, 20,
21, 22, 23, 24, 23, 24, 25, 26, 27, 28, 27, 28, 29, 30, 31, 0]
# 置换运算 P
P = [15, 6, 19, 20, 28, 11, 27, 16, 0, 14, 22, 25, 4, 17, 30, 9, 1, 7, 23, 13, 31, 26, 2, 8, 18, 12, 29, 5, 21, 10, 3,
24]
# S盒
sbox = [
# S1
[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
# S2
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
# S3
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
# S4
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
# S5
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
# S6
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
# S7
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
# S8
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
]
l = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1] # 循环左移位数
# 密钥置换选择1
def key_ds1(key):
s = ''
for i in key_table1:
s += key[i]
return s
# 密钥置换选择2
def key_ds2(key):
s = ''
for i in key_table2:
s += key[i]
return s
# 密钥循环左移
def key_move(key, r):
s = key
for i in range(l[r]):
s = s[1:] + s[:1]
return s
# 通过64位密钥获取全部的子密钥
def get_subkey(key_bin):
# 首先进行key_bin的奇偶校验检查
if not check_key(key_bin):
print('密钥奇偶校验不通过!')
return
key = []
# 密钥置换选择1
key1_res = key_ds1(key_bin)
L = key1_res[:28]
R = key1_res[28:]
for i in range(16):
# 循环左移
L = key_move(L, i)
R = key_move(R, i)
# 密钥置换选择2
key.append(key_ds2(L + R))
return key
# 扩展置换E
def extend_E(R):
r = ''
for i in E:
r += R[i]
return r
# 代换选择S盒
def alter_s(t):
j = 0
res = ''
for i in range(0, 48, 6):
c = int(t[i + 1:i + 5], 2)
r = int(t[i] + t[i + 5], 2)
res += '{:04b}'.format((sbox[j][r * 16 + c]))
j += 1
# print(res)
return res
# P置换
def p_repl(s):
p = ''
for i in P:
p += s[i]
return p
# DES加密基本函数,输入64位明文和密钥
def DES(M, key):
# 首先将明文进行初始置换IP
m = ''
for i in IP:
m += M[i]
L = []
R = []
# print('m=',m)
L.append(m[:32])
R.append(m[32:])
# 16轮feistel结构
for i in range(16):
L.append(R[i])
# 将R进行扩展置换E
R_extend = extend_E(R[i])
# 异或子密钥 K(i)
t = '{:048b}'.format(int(R_extend, 2) ^ int(key[i], 2))
# 代换选择S盒
s = alter_s(t)
# P置换
p = p_repl(s)
# 异或L(i-1)
r = '{:032b}'.format(int(p, 2) ^ int(L[i], 2))
R.append(r)
# 左右交换
c = R[16] + L[16]
# 逆初始置换
cipher = ''
for i in IP_1:
cipher += c[i]
return cipher
# DES加密函数
def DES_encrypt(message_bin_data, sub_key, i):
print('正在利用密钥', i, '对数据进行DES加密')
ciphertext = DES(message_bin_data, sub_key)
# print('加密后得到的二进制流为:', ciphertext, ' ', len(ciphertext), '位')
return ciphertext
# DES解密函数
def DES_decrypt(ciphertext, key, i):
print('正在利用密钥', i, '对数据进行DES解密')
plainbin = DES(ciphertext, key)
return plainbin
# 三重DES加密
def DES_3_encrypt(s, sub_key1, sub_key2, sub_key3):
sub_key2 = sub_key2[::-1] # 解密密钥
# print("*** 正在进行三重DES加密 ***")
ciphertext = DES_encrypt(s, sub_key1, 1)
ciphertext = DES_decrypt(ciphertext, sub_key2, 2)
ciphertext = DES_encrypt(ciphertext, sub_key3, 3)
return ciphertext
# 三重DES解密
def DES_3_decrypt(ciphertext, sub_key1, sub_key2, sub_key3):
# print("*** 正在进行三重DES解密 ***")
sub_key1 = sub_key1[::-1]
sub_key2 = sub_key2 # 加密密钥
sub_key3 = sub_key3[::-1]
ciphertext = DES_decrypt(ciphertext, sub_key3, 3)
ciphertext = DES_encrypt(ciphertext, sub_key2, 2)
plaintext = DES_decrypt(ciphertext, sub_key1, 1)
print("*** 三重DES解密成功 ***")
return plaintext
# CBC加密
def CBC_encrypt(group_list, iv, key_bin1, key_bin2, key_bin3):
print("*** 正在获取子密钥 ***")
# 获取子密钥
sub_key1 = get_subkey(key_bin1)
sub_key2 = get_subkey(key_bin2)
sub_key3 = get_subkey(key_bin3)
C_list = []
Iv = iv
print("*** 正在进行CBC加密 ***")
# 进行CBC加密
for group in group_list:
xor_res = '{:064b}'.format(int(group, 2) ^ int(Iv, 2))
C = DES_3_encrypt(xor_res, sub_key1, sub_key2, sub_key3)
C_list.append(C)
Iv = C
return C_list
# 存储加密后的分组信息
def save_img_data(list_data):
filename = 'encrypt_data.txt'
f = open(filename, 'w')
for i in list_data:
f.write(i + '\n')
f.close()
def save_iv_data(iv):
filename = 'iv.txt'
f = open(filename, 'w')
f.write(iv + '\n')
f.close()
def save_keys(key_bin1, key_bin2, key_bin3):
filename = 'keys.txt'
f = open(filename, 'w')
f.write(key_bin1 + '\n')
f.write(key_bin2 + '\n')
f.write(key_bin3 + '\n')
f.close()
# 获取加密图像的像素信息
def get_img_data():
img_data = ''
filename = 'encrypt_data.txt'
f = open(filename, 'r')
for line in f:
img_data += (line.strip('\n'))
f.close()
return img_data
# 基于三重DES的加密图像
def DES3_encryption():
global panelB
# 获取图像信息
# img = encryption_img["encryption"]
# encryptionImg = np.zeros(img.shape, np.uint8)
path = encryption_img["ImgPath"]
print("*** 正在获取图像信息 ***")
bin_data, img_width, img_height = get_pixel(path)
bin_data_list = set_group(bin_data)
# 保存明文分组信息,存储在group.txt文件中
print("*** 保存明文分组信息成功 ***")
print("*** 明文分组信息存储在group.txt文件中 ***")
save_group_data(bin_data_list)
group_list = get_group() # 获取分组列表
iv = get_IV() # 获取初始向量IV
# 随机生成3个DES密钥
key_bin1 = get_rand_key(1)
key_bin2 = get_rand_key(2)
key_bin3 = get_rand_key(3)
c_list = CBC_encrypt(group_list, iv, key_bin1, key_bin2, key_bin3) # 进行CBC模型下的三重DES加密
# CBC模型下的三重DES解密
save_img_data(c_list) # 保存加密后分组数据便于 4密文图像显示
save_iv_data(iv) # 保存初始向量IV信息用于解密
save_iv_data(iv) # 保存初始向量IV信息用于解密
# 保存解密数据
save_keys(key_bin1, key_bin2, key_bin3) # 保存密钥信息
img_data = get_img_data()
img = Image.new('RGB', (img_width, img_height))
c = 0
for i in range(img_width):
for j in range(img_height):
r = int(img_data[c:c + 8], 2)
g = int(img_data[c + 8:c + 16], 2)
b = int(img_data[c + 16:c + 24], 2)
# print('绘制加密图片',i,j,r,g,b,sep=' ')
img.putpixel((i, j), (r, g, b))
c += 24
arrayImage = img.resize((320, 230), Image.BILINEAR)
encryptionImg = ImageTk.PhotoImage(arrayImage)
panelB = Label(image=encryptionImg)
panelB.image = encryptionImg
panelB.place(x=(800 - panelB.winfo_reqwidth() - 10), y=(500 - panelB.winfo_reqheight()) / 2)
print("-----加密完成-----")
结果如下:
基于LSB的数字水印提取
LSB数字水印是一种常见的数字水印技术,通过将隐藏信息嵌入到图像的最低有效位中,实现对图像进行隐秘性的信息嵌入和提取。
用户可以在界面上方便地输入待提取的水印信息,并通过点击按钮来执行水印提取操作,从而实现基于LSB的数字水印提取功能。这种方法简单有效,适用于一些对水印提取速度要求不高、对图像质量要求不严格的应用场景。以下是数字水印实现的具体步骤:
窗口布局设置
这里我们在这里创建第三个窗口用于展示数字水印的功能,窗口布局的代码如下:
# 第三个窗口
def Three():
root3 = Tk()
root3.geometry('800x500+600+200')
root3.title('数字水印处理')
Label(root3, text='功能选择', bg='pink').pack(fill=X)
# 进入当前窗口的子窗体回调函数
def goto(num):
# 关闭主窗口
root3.destroy()
# 使用字典来映射数字和对应的窗口函数,然后在 goto 函数中进行查找和调用,如果找到则调用该函数,否则不执行任何操作。
windows = {
1: Three_one,
2: Three_two,
}
func = windows.get(num, lambda: None)
if func:
func()
but1 = Button(root3, text="返回主窗体", command=lambda: gotoMain(root3))
but1.pack(side="bottom", pady=10)
but2 = Button(root3, text="嵌入数字水印", font=('Arial', 12), command=lambda: goto(1)) # 进入窗口3-1
but2.pack(pady=(115, 15))
but3 = Button(root3, text="提取数字水印", font=('Arial', 12), command=lambda: goto(2)) # 进入窗口3-2
but3.pack(pady=15)
but4 = Button(root3, text="退出系统", font=('Arial', 12), command=lambda: LogDown(root3))
but4.pack(pady=15)
再创建一些子窗口用于图片水印的嵌入与提取:
最终展示的效果如下所示:
水印的嵌入与提取
下面这段代码实现了一个简单的基于LSB(Least Significant Bit,最低有效位)的数字水印嵌入和提取功能。下面是代码的实现思路:
嵌入水印回调函数 embed_watermark:
1)用户在输入框中输入待嵌入的水印信息。
2)用户选择一张要嵌入水印的图像文件(支持png、jpg、jpeg格式)。
3)打开选定的图像文件,并将其转换为RGB格式。
4)将用户输入的水印文本转换为二进制字符串。
5)检查水印长度是否超过图像容量,若超过则提示水印过长。
6)遍历每个像素位置,将水印信息的每一位嵌入到图像的红色通道的最低有效位。
7)保存嵌入水印后的图像文件(支持用户选择保存路径)。
![1e1](czimage/1e1.gif)# 嵌入水印回调函数
def embed_watermark(entry_text, label):
# 选择要嵌入水印的图像文件
image_path = filedialog.askopenfilename(filetypes=[("Image Files", "*.png;*.jpg;*.jpeg")])
if not image_path:
return
# 打开图像文件
image = Image.open(image_path).convert('RGB')
width, height = image.size
# 输入水印文本
watermark = entry_text
# 将水印转换为二进制字符串
binary_watermark = ''.join(format(ord(c), '08b') for c in watermark)
# 检查水印长度是否超过图像容量
if len(binary_watermark) > width * height * 3:
label["text"] = "Watermark is too long for the selected image."
return
# 嵌入水印
for i in range(len(binary_watermark)):
x = i % width
y = i // width
pixel = list(image.getpixel((x, y)))
bit = int(binary_watermark[i])
# 将最低有效位设置为水印比特
pixel[0] = (pixel[0] & 0xFE) | bit
image.putpixel((x, y), tuple(pixel))
# 保存嵌入水印后的图像
save_path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG Image", "*.png")])
if save_path:
image.save(save_path)
label["text"] = "Watermark embedded and image saved successfully."
实现的效果如下所示:
提取水印函数 extract_watermark:
1)用户选择一张含有水印的图像文件(支持png、bmp、jpg、jpeg格式)。
2)打开选定的图像文件,并将其转换为RGB格式。
3)遍历每个像素位置,提取图像红色通道的最低位作为水印信息。
4)将提取的水印信息转换为二进制字符串,再将二进制字符串转换为字符形式的水印文本。
5)创建一个包含滚动条和文本框的父容器,显示提取的水印文本信息。
def extract_watermark(root):
# 选择要提取水印的图像文件
image_path = filedialog.askopenfilename(filetypes=[("Image Files", "*.png;*.bmp;*.jpg;*.jpeg")])
if not image_path:
return
# 打开图像文件
image = Image.open(image_path).convert('RGB')
width, height = image.size
watermark = ""
# 提取水印
for y in range(height):
for x in range(width):
pixel = list(image.getpixel((x, y)))
bit = pixel[0] & 0x01 # 提取红色通道的最低位
watermark += str(bit)
# 将二进制字符串转换为字符
extracted_text = "".join(chr(int(watermark[i:i+8], 2)) for i in range(0, len(watermark), 8))
# 创建滚动条和文本框的父容器
frame = Frame(root)
frame.pack(expand=True, fill="both")
scrollbar = Scrollbar(frame)
scrollbar.pack(side="right", fill="y")
text_area = Text(frame, height=10, width=40)
text_area.insert("1.0", "Extracted Watermark:\n" + extracted_text)
text_area.pack(side="left", expand=True, fill="both")
# 将滚动条与文本框绑定
text_area.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=text_area.yview)
具体实现的效果请参考图像加密解密与数字水印处理该文章
小结
安全性考虑
在实现图像加密解密和数字水印处理时,安全性是首要考虑的因素。以下是一些关键的安全性考虑:
- 密钥管理:密钥是加密解密和数字水印处理的核心。必须确保密钥的安全存储和传输,避免泄露给未授权的人员。
- 算法选择:选择经过广泛验证和测试的加密算法和水印算法,以确保其安全性和可靠性。
- 鲁棒性:加密解密和数字水印处理算法应具有一定的鲁棒性,能够抵抗各种攻击和篡改尝试。
- 性能测试:对实现的加密解密和数字水印处理算法进行性能测试,确保其在实际应用中具有足够的效率和可扩展性。