设计与实现一款局域网中点到点(即一台计算机到另外一台计算机)的信息传输工具,要求能够保证信息在传输过程中的保密性、完整性和发送/接收方的不可否认性,以防止通信线路上的窃听、泄露、破坏、篡改等不良操作。
** 开发语言、开发平台、开发工具模块的选择**
该实验要求为设计完成一款文件安全传输软件,前端要求界面友好操作舒适,功能要求实现通信、加密、传输。综合考虑,选择使用Python作为开发语言。因为Python自身带有许多常用工具模块,且用于加密解密的安全库、用于通信传输的socket库也齐全,因此非常方便用于开发软件,结合使用可以节约开发时间,减少代码量。本人常用于编写Python的IDE为Visual Studio Code,因此本实验也通过VS Code完成。因为使用Python编程,所以软件界面设计选择使用Python库PyQt5,不过需要另外安装。PyQt是一个创建GUI应用程序的工具包。它是Python编程语言和Qt库的成功融合。Qt库是目前最强大的库之一。
总结如下:
开发顺序与大致框架
主要顺序是先前端再后端。设计好前端界面后,可以对应前端界面的按键、内容显示等部署后端功能,方便操作;相反,如果先完成后端功能,再设计前端界面,有可能出现前后端功能不匹配的问题,或是要重复修改后端功能,无疑增加了工作量。
前端部分,即软件界面设计。根据实验对于软件的功能要求,需要设计出与用户交互的按键、输入框,以及内容显示栏、提示框等。具体包括:发送键、接收键、取消键、IP地址输入框、端口选择框、文件选择栏、接收文件信息反馈栏、状态提示框、页面切换键、进度条等。
后端部分,即功能实现,可根据实验提供的“文件安全传输流程”图完成。实现顺序:
自定义要素和功能
“单文件安全传输”=>“多文件安全传输”
实验中没有明确要求是单文件传输或是多文件传输,考虑到软件设计要人性化,我才设定为“多文件安全传输”,可以一次性选择多文件进行安全发送。
** 主线程和分线程**
主线程运行软件窗口界面,而分线程分为了4个:服务端连接线程、客户端连接线程、服务端发送线程、客户端接收线程,分别负责实现不同的功能。
之所以要多线程运行,是由于等待连接和等待传输的过程如果放在主线程中,会造成界面假死的状态,因此为了避免这种情况发生,采用多线程方式来运行通信连接和文件传输的步骤。
文件头传输
发送文件之前会先发送文件头,文件头中包含文件名和文件大小以及当前传输状态(正常、完成、出错)。
文件头是提前告知接收方的信息。文件名用于接收文件的命名;文件大小让接收方获知需要接收多少的数据;当前传输状态让接收方获知多文件传输是否传输完成。
文件接收状态反馈
接收方在接收完文件后需要对文件进行数据完整性的检验(即对比摘要内容),当比对结果出现错误,即表示当前接收的文件可能被篡改,当前通信并不安全,需要重新生成密钥并交换,建立新的安全通信连接。
由于我将“单文件安全传输”优化为了“多文件安全传输”,因此发送方不能够一次将全部文件都发送给接收方,而应该等待接收方对当前文件作出反馈,表示该文件数据完整性是否良好,再进一步发送下一个文件,否则重新建立连接并重传该文件。
界面设计
界面设计使用Qt平台,Qt平台中提供有图形化界面进行界面设计,十分方便。如下图为“发送文件”界面的设计图。
如下图为“接收文件”界面的设计图。
另存为文件“file.ui”。
界面(前端)代码生成
Python中提供pyuic5工具,可以将file.ui的界面布局文件转换成Python代码,免去了繁琐的敲代码生成界面的过程。命令为“pyuic5 -o file.py file.ui”表示源文件为file.ui生成目标文件file.py。请注意,此时生成的代码file.py仅仅实现了界面显示,并未实现软件后端的功能。
编写(后端)功能代码
前端代码生成后,根据前端界面的控件名称,在后端代码实现通信传输功能的同时控制前端界面显示相应内容。
程序打包
代码编写后的文件为.py文件,可以使用Python工具PyInstaller,将代码以及相关依赖库打包成.exe程序文件。命令为“pyinstaller -F -i favicon.ico run.py -w”,-F表示打包为单文件程序,-i表示绑定程序图标为favicon.ico,run.py为主程序代码,-w表示程序运行时不显示命令行窗口。具体过程如下:
代码清单
关键代码分析
# 发送过程
def sendPro(self, fileIndex):
if fileIndex != -1: # 重传操作
self.sendFileCount = fileIndex
# 遍历文件列表
for index in range(self.sendFileCount, len(self.files)):
# 判断是否按了取消键
if not self.mainWin.sendState:
return False, -1
file = self.files[index]
de = DEncry() # 创建DES加密对象
des_filename = de.des_encrypt_file(file) # DES加密文件
# md5_filename = md5_encrypt_file(file) # 生成文件摘要
filename = self.packupFile(
file, des_filename) # 打包DES密钥、加密文件、签名摘要,参数:原文件、DES加密文件
head_info_len, head_info = self.operafile(filename, file) # 生成文件头
self.conn.send(head_info_len) # 发送文件头长度,4个字节
self.conn.send(head_info.encode('utf-8')) # 文件头编码,发送文件头的内容
self.sendRealFile(filename, self.filesize_bytes) # 发送打包文件
# 设置进度条显示
progress = (index+1) / len(self.files) * 100
self.mainWin.totalpro_progressBar.setValue(progress)
# 等待接收端回复文件状态
while True:
send_file_state_len = self.conn.recv(4) # 接收发送状态的长度
if len(send_file_state_len) == 4:
send_file_state_len = struct.unpack(
'i', send_file_state_len)[0]
# 接收指定长度的发送状态
send_file_state = self.conn.recv(send_file_state_len)
send_file_state = json.loads(
send_file_state.decode('utf-8')) # 发送状态解码
# 断言当前文件索引与发送状态中的文件索引一致
assert send_file_state['fileIndex'] == index
# 失败,返回False和对应文件索引
if not send_file_state['state']:
return False, send_file_state['fileIndex']
break
# 发送完毕
if index == len(self.files) - 1:
head_info_len, head_info = self.operafile() # 生成“发送结束”的文件头
self.conn.send(head_info_len) # 发送文件头长度,4个字节
self.conn.send(head_info.encode('utf-8')) # 文件头编码,发送文件头的内容
return True, -1 # 成功发送所有文件则返回true和文件索引为-1
# 接收文件过程
def RecvPro(self, fileIndex):
if fileIndex != -1: # 重传操作
self.recFileCount = fileIndex
while True:
try:
self.conn.settimeout(5)
# self.mainWin.request_Button.setEnabled(False) # 开始监听按钮无效化
self.mainWin.cancel_Button2.setEnabled(True) # 取消按钮有效化
while True:
# 判断是否按了取消键
if self.mainWin.recvState == False:
# self.request_Button.setEnabled(True) # 开始监听按钮有效化
break
struct_len = self.conn.recv(4) # 接收文件头的长度
if len(struct_len) == 4:
self.mainWin.state_label2.setText("状态:正在接收文件...")
head_dir = self.getFileHeader(
self.conn, struct_len) # 接收文件头
if head_dir['filestate'] == 'transiting': # 文件头属性为接收状态
# # 文件信息
# filename = head_dir['filename']
# filesize = head_dir['filesize_bytes']
lineEdit = '正在接收第' + str(self.recFileCount + 1) + \
'号文件【' + head_dir['filename'] + '】......'
self.recFileList(lineEdit) # 显示正在接收信息
self.recv_file(head_dir, self.conn) # 开始接收文件
self.recvFileState(True) # 发送文件接收状态:成功
self.recFileCount += 1 # 接收文件数加1
lineEdit = '成功接收第' + str(self.recFileCount) + \
'号文件【' + head_dir['filename'] + '】 --> ' + \
os.getcwd() + '\\' + head_dir['filename']
self.recFileList(lineEdit) # 显示接收信息
elif head_dir['filestate'] == 'final': # 文件头属性为完成接收状态
self.mainWin.recvState = False
lineEdit = '已完成文件接收!'
self.recFileList(lineEdit) # 显示正在接收信息
self.mainWin.state_label2.setText("状态:完成文件接收")
break
else: # 文件头属性为错误状态
self.mainWin.recvState = False
lineEdit = '接收文件过程中发生错误!'
self.recFileList(lineEdit) # 显示正在接收信息
self.mainWin.state_label2.setText("状态:发生未知错误")
break
except rsa.VerificationError: # rsa验签抛出错误
self.recvFileState(False) # 发送文件接收状态:失败/错误
lineEdit = '对比接收文件摘要不符,验签失败!准备重传文件...'
self.recFileList(lineEdit) # 显示正在接收信息
self.mainWin.state_label2.setText("状态:准备重连...")
return False, self.recFileCount # 返回false和当前已接收文件数
except BaseException: # 如果建立连接后,该连接在设定的时间内无数据发来,则time out
self.mainWin.state_label2.setText("状态:已连接,等待文件...")
if self.mainWin.recvState == False:
break
return True, -1 # 成功接收所有文件则返回true和文件索引为-1
功能介绍
功能验证
注意事项:打包后的软件,暂时无法在Windows XP、Windows 2003以下的版本中运行,只能在Windows 7和Windows 10中运行。
验证环境:两台局域网主机。
安全通信连接:生成密钥、交换密钥
打开软件所在文件夹,可以看到有一个文件夹icon和一个可执行文件run.exe。其中icon文件夹中包含程序图标以及图片,需要与run.exe放置在同一文件夹下方可使用。
双击启动run.exe程序,就开始运行该软件了。可以看到文件夹中多了一个新的文件夹“.tempfile”,这个文件夹用于存放通信传输使用的公私密钥、对称密钥以及产生的临时文件,此时该文件夹仍为空。
服务端(监听方/发送方)主机IP为172.27.24.67,客户端(请求方/接收方)主机IP为172.27.24.68。在发送端处运行发送界面,输入发送端IP 172.27.24.67,端口8080,点击“监听端口”,便开始监听本机8080端口。当接收端也发出了请求,则出现如下界面,表示成功连接并已生成RSA公私密钥对和交换RSA公钥。
同理,在接收端处运行接收界面。同样,输入发送端IP 172.27.24.67,端口8080,点击“准备接收”,就会向发送端主机的8080端口发出请求,稍后便会显示“已连接”。表示连接已建立并交换了密钥。
查看发送端的临时文件夹.tempfile,可以看到有3个RSA密钥文件,分别是:对方(即接收端)公钥、自己的私钥、自己的公钥。
查看接收端的临时文件夹.tempfile,可以看到有3个RSA密钥文件,分别是:对方(即发送端)公钥、自己的私钥、自己的公钥。
上述步骤说明了该软件完成了通信连接、RSA密钥生成以及密钥交换的功能。
文件安全传输:加密文件、解密文件、文件摘要比对
首先在发送端处,添加文件,这里为演示方便,仅添加了两个小型文件.txt和.png,添加完成后会在下方的文件列表中显示对应的文件路径以及文件大小,点击发送即可。
注1:由于DES加密解密的原因,选择几十MB以上的文件进行传输会导致运行速度很慢,看上去像“程序假死”,但其实软件一直在运行,只是还在加密解密文件的过程中。我尝试过优化/更换DES加密解密算法,但效果不佳。为演示方便,在此使用文件大小较小的文件做演示。
注2:此处还能使用“删除”键和“清空”键,“删除”键需要选择文件列表中某一行后才可删除,“清空”键可直接点击则会一次性删除所有文件。
发送完成后,发送端界面如下所示。文件列表清空,进度条显示100%,提示框提示:发送完成。
接收端界面如下所示。内容显示框中显示了对应接收文件的存放路径,进度条显示100%,提示框提示:接收完成。
根据显示的存放路径找到接收的文件,即存放在接收端的软件运行的当前文件夹中。为了以示区别,我在接收的文件都加上了前缀名“new”,以与原文件进行区分。
再次查看发送端的.tempfile文件夹,可以发现新增了4个文件,分别是1.txt经过DES加密后的临时文件(密文)、titleicon.png经过DES加密后的临时文件(密文)、DES密钥文件(明文)、打包准备发送的文件(密文)。
查看接收端的.tempfile文件夹,可以发现新增了2个文件,分别是经过RSA解密后的DES密钥文件(明文)、接收的原始文件(密文)。
文件摘要比对失败后重连并重传文件
注:由于实际演示中,较难模拟黑客攻击篡改了文件,因此我从源代码处进行修改,将原本摘要比对正确的情况,修改为摘要比对错误的情况。
功能描述:当出现摘要比对错误的情况,接收端会反馈给发送端,此时接收端和发送端会立即终止当前连接,重新生成密钥、重新建立连接、重新交换密钥,最后重传该文件。重新建立连接的过程中,会更换与上一次连接不同的端口。
演示中,使用的是127.0.0.1的本机IP,即自己跟自己进行安全通信传输。接收端中不断显示“摘要比对错误”,因此与发送端不断进行重连(包括重新产生RSA公私密钥对和重新交换公钥)和重传文件,期间双方的通信端口不断作加一更换,从8080到8088。
为方便演示该软件的功能,可在一台主机上启动该软件,默认使用发送界面和接收界面的IP:127.0.0.1,实际效果便是主机自己与自己进行通信传输。
然后便可正常使用连接、文件传输功能了,只需来回切换“发送文件”界面和“接收文件”界面即可。
通过本次实验,加深了对于网络传输涉密文件过程中的安全性问题的理解,掌握了DES加/解密、RSA加/解密、MD5哈希等密码学概念和算法原理,熟悉了文件安全传输的整体流程。实践中,大量的代码量编写锻炼了我对于程序调试的综合能力,从软件设计中新涉猎到了QT这样的设计平台,对于软件工程这门学科也有了新的理解。收获甚多,仍需努力。
参考资料
Bookmarks
Bookmarks
书签栏
文件安全传输软件实现参考资料
- 信息的安全传输实验_kynehc-CSDN博客
界面编程
- 七个Python图形应用GUI开发框架
- Python跨平台图形界面编程库推荐
- python PyQt5 教程_zhangji-CSDN博客
- Python制作小软件——1. 安装并使用PyQt5进行界面设计_Kanny-CSDN博客
- Python制作小软件——2. 实现界面中的退出功能_Kanny-CSDN博客
- Python制作小软件——3. 利用PyQt5实现界面中的功能_Kanny-CSDN博客
- 解决PyQt designer启动运行问题
- vscode+PyQt+QtDesigner配置
- 把两个界面通过信号槽连接
- 文件传输界面参考
- Qt Designer工具的使用案例
- Qt项目界面文件(.ui)及其作用
- QtDesigner控件+槽函数
- QPushButton互斥选择
- 文件选择
- 文件列表显示QListWidget
- QTableWidget清空或删除内容功能1
- QTableWidget行选中/删除/添加行2
- 获取/设置QLabel/lineEdit内容
- 获取QSpinBox值
- 判断是否是IP
- PyQt5+socket编程界面卡住未响应
- threading多参数传递方法
- BaseRequestHandler 传参问题
- python-os模块拆分文件路径和文件名
- struct解包时报错 struct.error: unpack requires a buffer of 4 bytes
- 断开多线程(TcpSocketServer连接)
- pyqt5在textBrowser添加文本并自动滑动到底
- 实现socket信息发送与监听
- socket先后建立连接/等待连接问题
- Python创建目录文件夹 - itprobie-菜鸟程序员 - 博客园
- 基于pyqt5 构建弹窗进度条,在大型计算中实时显示进度_txh3093的博客-CSDN博客
python局域网文件传输
DES加密
- python pyDes加密解密-凌的博客
- DES(python) - 程序媛墨禾 - 博客园
- Python使用DES加密解密 - LivPzs - 博客园
- python中的md5加密 - lanston - 博客园
- 使用python生成rsa密钥对_code_lab-CSDN博客
- python3 RSA加解密_whatday的专栏-CSDN博客
- (1条消息) python随机生成包含字母数字的六位验证码_l_d_56的博客-CSDN博客
- python中cryptodome的DES_One of them的博客-CSDN博客
- Python DES 加密解密,就是大家所谓想要的那个非常快速的方法_weixin_30758821的博客-CSDN博客
多线程
- (1条消息) 【Python】socket同时收发与多线程防止input阻塞_bfz_50的博客-CSDN博客
- (1条消息) PyQt5多线程刷新界面防假死_徐奕的专栏-CSDN博客
- 【PyQt5-Qt Designer】pyqtSignal()-高级自定义信号与槽 - XJT2019 - 博客园
- (1条消息) QThread的用法:开启与退出_Like a lunatic-CSDN博客
- 如何从QThread和Queu运行的函数返回值 - 问答 - Python中文网
- python3 关于 Socket 关闭后 地址被重复使用的问题解决 | 旧城以西
- python socket 编程之三:长连接、短连接以及心跳 - 药师Aric - 博客园
界面美化
- pyqt5美化界面
- PyQt5 按钮QSS美化集_集电极-CSDN博客
- Qt中漂亮的几款QSS - 夜&枫 - 博客园
- css background-image 背景图片自适应宽高_yechaoa-CSDN博客
- QT实现背景图片多种填充方式:居中、平铺、缩放、拉伸_bochen_cwx的博客-CSDN博客
- Python-PyQt5-背景图
- ModuleNotFoundError: No module named 'Crypto'_木下瞳的博客-CSDN博客
- 9. PKI - 三种密钥交换算法详解(RSA& DHE& ECDHE)及他们在SSL/TLS协议中的应用 - it610.com