局域网文件安全传输——软件实现

时间:2024-03-04 12:48:42

实验目的

  • 设计安全的信息传输工具,解决网络传输涉密文件过程中的安全性问题。安全的信息传输,涉及多个密码学知识点,在实验设计过程中,不断加深理解密码学基本概念和算法基础原理,并且能够锻炼独立的代码编写能力和知识的综合运用能力。


实验仪器或设备

  1. 硬件:计算机1台
  2. 操作系统:Windows 10
  3. 软件/平台:Visual Studio Code、QT
  4. 编程语言:Python


实验内容

设计与实现一款局域网中点到点(即一台计算机到另外一台计算机)的信息传输工具,要求能够保证信息在传输过程中的保密性、完整性和发送/接收方的不可否认性,以防止通信线路上的窃听、泄露、破坏、篡改等不良操作。

  • 采用对称与非对称混合加密方法,每进行一次通信,更改一次会话密钥(用于信息加密的对称密钥)。
  • 能够实现对任意类型的文件进行安全传输。
  • 可采用适当的开发语言和开发工具。
  • 软件用户界面友好,提示信息完整,操作舒适。
  • 提交实验报告、包括分析和设计文档及代码清单。
  • 平台不限。linux/windows/android



实验步骤

设计思路+设计文档

    开发语言、开发平台、开发工具模块的选择

  该实验要求为设计完成一款文件安全传输软件,前端要求界面友好操作舒适,功能要求实现通信、加密、传输。综合考虑,选择使用Python作为开发语言。因为Python自身带有许多常用工具模块,且用于加密解密的安全库、用于通信传输的socket库也齐全,因此非常方便用于开发软件,结合使用可以节约开发时间,减少代码量。本人常用于编写Python的IDE为Visual Studio Code,因此本实验也通过VS Code完成。因为使用Python编程,所以软件界面设计选择使用Python库PyQt5,不过需要另外安装。PyQt是一个创建GUI应用程序的工具包。它是Python编程语言和Qt库的成功融合。Qt库是目前最强大的库之一。
总结如下:

  1. 通信、传输:socket库
  2. DES加密解密:pyDes库或Crypto库
  3. RSA加密解密:rsa库
  4. MD5哈希:md5库(可选,因为rsa库本身也有签名+摘要+验签的功能)
  5. 软件界面:PyQt5库(包含界面设计、多线程等)
  6. 常用库:random(生成随机数)、os(文件操作)、re(正则表达式)、json(编解码)、sys(系统操作)、threading(多线程)、time(时间)等

    开发顺序与大致框架

  主要顺序是先前端再后端。设计好前端界面后,可以对应前端界面的按键、内容显示等部署后端功能,方便操作;相反,如果先完成后端功能,再设计前端界面,有可能出现前后端功能不匹配的问题,或是要重复修改后端功能,无疑增加了工作量。
  前端部分,即软件界面设计。根据实验对于软件的功能要求,需要设计出与用户交互的按键、输入框,以及内容显示栏、提示框等。具体包括:发送键、接收键、取消键、IP地址输入框、端口选择框、文件选择栏、接收文件信息反馈栏、状态提示框、页面切换键、进度条等。
  后端部分,即功能实现,可根据实验提供的“文件安全传输流程”图完成。实现顺序:

  1. 启动前端界面,主线程。包含实现按键函数、读取输入信息、显示提示信息。
  2. 服务端(监听+发送方)与客户端(请求+接收方)的通信,分为服务端一个新的通信线程和客户端一个新的通信线程。包含通信连接、生成密钥、密钥交换的操作。
  3. 服务端与客户端的信息传输,分为服务端一个新的发送线程和客户端一个新的接收线程。包含加/解密文件、生成摘要、签名、打包文件、发送/接收的操作。

    自定义要素和功能

  1. 将“单文件安全传输”优化为了“多文件安全传输”。
  2. 发送文件前首先发送文件头,其中包含文件名和文件大小等属性。
  3. 文件接收后反馈文件接收状态后再继续发送下一文件,以确保是否需要重传。

实验分析(分析为什么这样设计、安排)

    “单文件安全传输”=>“多文件安全传输”

  实验中没有明确要求是单文件传输或是多文件传输,考虑到软件设计要人性化,我才设定为“多文件安全传输”,可以一次性选择多文件进行安全发送。

    主线程和分线程

  主线程运行软件窗口界面,而分线程分为了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表示程序运行时不显示命令行窗口。具体过程如下:




实验代码清单+关键代码分析

    代码清单

  1. run.py:主程序代码,负责生成软件主窗口,以及各种按键函数。还包含四个分线程类(服务器连接、客户端连接、服务器发送、客户端接收)。
  2. sendfile.py:发送操作类,包含所有的发送文件操作。
  3. recfile.py:接收操作类,包含所有的接收文件操作
  4. des.py:DES加解密操作。
  5. rsa1.py:RSA加解密操作。
  6. md5.py(可选):MD5哈希操作。
  7. utility.py:其他工具函数,如生成目录、生成随机数、判断输入内容是否为IP地址。
  8. file.py:file.ui生成的界面代码,由run.py运行启动。
  9. 注1:代码文件中有详细的代码注释,可以直接查看代码文件进行阅读。

    关键代码分析

  • sendfile.py的Sender类的sendPro函数
  1. 函数首先判断当前是否是重传文件的操作,若是,则恢复到之前的状态。
  2. for循环遍历需要上传的文件。
  3. 使用DES生成DES密钥的同时,加密当前文件。
  4. packupFile函数中包含RSA加密DES密钥、MD5哈希生成文件摘要、打包文件的三个操作。
  5. 根据打包后的文件,生成相应的文件头,文件头包含文件名称、文件大小。
  6. 发送文件头大小,再发送文件头,接着发送打包文件。
  7. 发送完打包文件后,修改发送界面的进度条显示进度。
  8. 每发送完一个文件,在while循环中等待接收方对接收的文件作出反馈。接收的反馈内容与文件头类似,需要先获取长度再获取内容并且解码。如果反馈正常,则继续下一步,否则直接返回错误False和当前错误文件的索引,准备重新建立连接并重传文件。
  9. for循环的最后,判断是否发送结束,若是,则发送“完成”的文件头告知接收方;否则继续循环发送文件。
  # 发送过程
    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

  • recfile.py的Recver类的recvPro函数
  1. 函数首先判断当前是否是重传文件的操作,若是,则恢复到之前的状态。
  2. 第一个while循环不断等待接收文件,期间会抛出timeout异常。
  3. 第二个while循环则不断接收文件,并在开头判断是否按下了“取消”的按键,若是则停止接收文件。
  4. 接着,接收文件头长度,并根据长度接收文件头,获取文件名、文件大小和发送状态。
  5. 若发送状态为“transiting”,则表示还有文件需要接收,则进行接收操作,由recv_file函数负责完成,其中包含解密、验签、比对摘要等操作。接收后,还需要返回主界面相关的显示信息,并且反馈给发送端当前文件状态是否正常是否需要重传。
  6. 若发送状态为“final”,则返回主界面相关的显示信息,并结束接收文件。
  7. 若发送状态为“error”,则马上停止接收,并返回出错信息。
  8. 两个except用于处理抛出的异常。一个是rsa.VerificationError,即比对摘要时出现不一致,会反馈给发送端需要重传,并且返回False和当前错误文件索引给客户端表示需要重新建立连接。另一个是BaseExcption,包含“等待接收文件”的超时异常,进行相关提示信息的显示,表示对方还未发送文件。
  # 接收文件过程
    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这样的设计平台,对于软件工程这门学科也有了新的理解。收获甚多,仍需努力。


    参考资料

  • 将如下代码拷入空.html文件,形成书签文件,从浏览器“书签管理器”导入即可。分类不足,仅供参考,包含关键技术以及踩坑解决方案。
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<!-- This is an automatically generated file.
     It will be read and overwritten.
     DO NOT EDIT! -->
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>
<DL><p>
    <DT><H3 ADD_DATE="1585267644" LAST_MODIFIED="1604585852" PERSONAL_TOOLBAR_FOLDER="true">书签栏</H3>
    <DL><p>
        <DT><H3 ADD_DATE="1605107979" LAST_MODIFIED="1605107979">文件安全传输软件实现参考资料</H3>
        <DL><p>
            <DT><A HREF="https://blog.csdn.net/chen_yi_ang/article/details/87284323" ADD_DATE="1605107979">信息的安全传输实验_kynehc-CSDN博客</A>
            <DT><H3 ADD_DATE="1605107979" LAST_MODIFIED="1605107979">界面编程</H3>
            <DL><p>
                <DT><A HREF="https://blog.csdn.net/tTU1EvLDeLFq5btqiK/article/details/78693348?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param" ADD_DATE="1605107979">七个Python图形应用GUI开发框架</A>
                <DT><A HREF="https://www.zhihu.com/question/19555504" ADD_DATE="1605107979">Python跨平台图形界面编程库推荐</A>
                <DT><A HREF="https://blog.csdn.net/m0_37329910/article/details/86557942" ADD_DATE="1605107979">python PyQt5 教程_zhangji-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/weixin_41929524/article/details/81456308" ADD_DATE="1605107979">Python制作小软件——1. 安装并使用PyQt5进行界面设计_Kanny-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/weixin_41929524/article/details/81460203?utm_medium=distribute.pc_relevant.none-task-blog-title-4&spm=1001.2101.3001.4242" ADD_DATE="1605107979">Python制作小软件——2. 实现界面中的退出功能_Kanny-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/weixin_41929524/article/details/81475935?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param" ADD_DATE="1605107979">Python制作小软件——3. 利用PyQt5实现界面中的功能_Kanny-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/weixin_42061064/article/details/108051962?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param" ADD_DATE="1605107979">解决PyQt designer启动运行问题</A>
                <DT><A HREF="https://www.cnblogs.com/codeofmine/p/12637880.html" ADD_DATE="1605107979">vscode+PyQt+QtDesigner配置</A>
                <DT><A HREF="https://zhidao.baidu.com/question/2051943530923041467.html" ADD_DATE="1605107979">把两个界面通过信号槽连接</A>
                <DT><A HREF="https://www.cnblogs.com/luoxiang/p/13452889.html" ADD_DATE="1605107979">文件传输界面参考</A>
                <DT><A HREF="https://www.jb51.net/article/167015.htm" ADD_DATE="1605107979">Qt Designer工具的使用案例</A>
                <DT><A HREF="http://c.biancheng.net/view/1820.html" ADD_DATE="1605107979">Qt项目界面文件(.ui)及其作用</A>
                <DT><A HREF="https://blog.csdn.net/yogima/article/details/74012112#1%E5%8F%AF%E4%BD%BF%E7%94%A8%E6%8F%90%E4%BE%9B%E7%9A%84%E6%A7%BD%E5%87%BD%E6%95%B0clear%E6%88%96lower" ADD_DATE="1605107979">QtDesigner控件+槽函数</A>
                <DT><A HREF="https://blog.csdn.net/amwha/article/details/85873921" ADD_DATE="1605107979">QPushButton互斥选择</A>
                <DT><A HREF="https://blog.csdn.net/humanking7/article/details/80546728" ADD_DATE="1605107979">文件选择</A>
                <DT><A HREF="https://www.cnblogs.com/liming19680104/p/10373966.html" ADD_DATE="1605107979">文件列表显示QListWidget</A>
                <DT><A HREF="https://blog.csdn.net/weixin_38403778/article/details/82464181" ADD_DATE="1605107979">QTableWidget清空或删除内容功能1</A>
                <DT><A HREF="https://www.cnblogs.com/felix-wang/p/6210184.html" ADD_DATE="1605107979">QTableWidget行选中/删除/添加行2</A>
                <DT><A HREF="https://www.cnblogs.com/ygzhaof/p/10057722.html" ADD_DATE="1605107979">获取/设置QLabel/lineEdit内容</A>
                <DT><A HREF="https://blog.csdn.net/weixin_43496130/article/details/104242307" ADD_DATE="1605107979">获取QSpinBox值</A>
                <DT><A HREF="https://www.cnblogs.com/ccz320/p/6536993.html" ADD_DATE="1605107979">判断是否是IP</A>
                <DT><A HREF="https://blog.csdn.net/wbdxz/article/details/86290139" ADD_DATE="1605107979">PyQt5+socket编程界面卡住未响应</A>
                <DT><A HREF="https://www.cnblogs.com/chenfulin5/p/10307754.html" ADD_DATE="1605107979">threading多参数传递方法</A>
                <DT><A HREF="https://payloads.online/archivers/2019-04-02/2" ADD_DATE="1605107979">BaseRequestHandler 传参问题</A>
                <DT><A HREF="https://www.cnblogs.com/lutt/p/12115755.html" ADD_DATE="1605107979">python-os模块拆分文件路径和文件名</A>
                <DT><A HREF="https://blog.csdn.net/Dontla/article/details/103823134" ADD_DATE="1605107979">struct解包时报错 struct.error: unpack requires a buffer of 4 bytes</A>
                <DT><A HREF="https://blog.csdn.net/qq_32446743/article/details/80309836" ADD_DATE="1605107979">断开多线程(TcpSocketServer连接)</A>
                <DT><A HREF="https://www.cnblogs.com/zwgbk/p/10268398.html" ADD_DATE="1605107979">pyqt5在textBrowser添加文本并自动滑动到底</A>
                <DT><A HREF="https://www.jb51.net/article/132057.htm" ADD_DATE="1605107979">实现socket信息发送与监听</A>
                <DT><A HREF="https://blog.csdn.net/Quanworld/article/details/96101051?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param" ADD_DATE="1605107979">socket先后建立连接/等待连接问题</A>
                <DT><A HREF="https://www.cnblogs.com/guohu/p/11320008.html" ADD_DATE="1605107979">Python创建目录文件夹 - itprobie-菜鸟程序员 - 博客园</A>
                <DT><A HREF="https://blog.csdn.net/txh3093/article/details/108562805" ADD_DATE="1605107979">基于pyqt5 构建弹窗进度条,在大型计算中实时显示进度_txh3093的博客-CSDN博客</A>
            </DL><p>
            <DT><H3 ADD_DATE="1605107979" LAST_MODIFIED="1605107979">python局域网文件传输</H3>
            <DL><p>
                <DT><A HREF="https://www.cnblogs.com/xiaokang01/p/9069048.html" ADD_DATE="1605107979">python socket 传输文件 - Lucky&amp; - 博客园</A>
                <DT><A HREF="https://www.jb51.net/article/144628.htm" ADD_DATE="1605107979">python使用tcp实现局域网内文件传输_python_脚本之家</A>
            </DL><p>
            <DT><H3 ADD_DATE="1605107979" LAST_MODIFIED="1605107979">DES加密</H3>
            <DL><p>
                <DT><A HREF="http://www.jiuaitu.com/python/421.html" ADD_DATE="1605107979">python pyDes加密解密-凌的博客</A>
                <DT><A HREF="https://www.cnblogs.com/Erma/p/12350416.html" ADD_DATE="1605107979">DES(python) - 程序媛墨禾 - 博客园</A>
                <DT><A HREF="https://www.cnblogs.com/lvpzs/p/des.html" ADD_DATE="1605107979">Python使用DES加密解密 - LivPzs - 博客园</A>
                <DT><A HREF="https://www.cnblogs.com/lanston1/p/11025881.html" ADD_DATE="1605107979">python中的md5加密 - lanston - 博客园</A>
                <DT><A HREF="https://blog.csdn.net/sinat_25449961/article/details/78039656" ADD_DATE="1605107979">使用python生成rsa密钥对_code_lab-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/whatday/article/details/97617461/" ADD_DATE="1605107979">python3 RSA加解密_whatday的专栏-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/l_d_56/article/details/84799457" ADD_DATE="1605107979">(1条消息) python随机生成包含字母数字的六位验证码_l_d_56的博客-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/One_of_them/article/details/82049232" ADD_DATE="1605107979">python中cryptodome的DES_One of them的博客-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/weixin_30758821/article/details/98288238" ADD_DATE="1605107979">Python DES 加密解密,就是大家所谓想要的那个非常快速的方法_weixin_30758821的博客-CSDN博客</A>
            </DL><p>
            <DT><H3 ADD_DATE="1605107979" LAST_MODIFIED="1605107979">多线程</H3>
            <DL><p>
                <DT><A HREF="https://blog.csdn.net/bfz_50/article/details/93391672" ADD_DATE="1605107979">(1条消息) 【Python】socket同时收发与多线程防止input阻塞_bfz_50的博客-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/xyisv/article/details/88292870?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-4-88292870.nonecase&utm_term=pyqt%20%E9%98%BB%E5%A1%9E%E7%95%8C%E9%9D%A2" ADD_DATE="1605107979">(1条消息) PyQt5多线程刷新界面防假死_徐奕的专栏-CSDN博客</A>
                <DT><A HREF="https://www.cnblogs.com/XJT2018/p/10222981.html" ADD_DATE="1605107979">【PyQt5-Qt Designer】pyqtSignal()-高级自定义信号与槽 - XJT2019 - 博客园</A>
                <DT><A HREF="https://blog.csdn.net/noricky/article/details/80048561" ADD_DATE="1605107979">(1条消息) QThread的用法:开启与退出_Like a lunatic-CSDN博客</A>
                <DT><A HREF="https://www.cnpython.com/qa/78282" ADD_DATE="1605107979">如何从QThread和Queu运行的函数返回值 - 问答 - Python中文网</A>
                <DT><A HREF="https://www.imsonggg.com/archives/121" ADD_DATE="1605107979">python3 关于 Socket 关闭后 地址被重复使用的问题解决 | 旧城以西</A>
                <DT><A HREF="https://www.cnblogs.com/xilouch/p/4618903.html" ADD_DATE="1605107979">python socket 编程之三:长连接、短连接以及心跳 - 药师Aric - 博客园</A>
            </DL><p>
            <DT><H3 ADD_DATE="1605107979" LAST_MODIFIED="1605107979">界面美化</H3>
            <DL><p>
                <DT><A HREF="https://blog.csdn.net/Nirvana_6174/article/details/88427071" ADD_DATE="1605107979">pyqt5美化界面</A>
                <DT><A HREF="https://blog.csdn.net/qq_38463737/article/details/107955083?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param" ADD_DATE="1605107979">PyQt5 按钮QSS美化集_集电极-CSDN博客</A>
                <DT><A HREF="https://www.cnblogs.com/newstart/p/4370203.html" ADD_DATE="1605107979">Qt中漂亮的几款QSS - 夜&amp;枫 - 博客园</A>
                <DT><A HREF="https://blog.csdn.net/yechaoa/article/details/89092914" ADD_DATE="1605107979">css background-image 背景图片自适应宽高_yechaoa-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/bochen_cwx/article/details/82083570" ADD_DATE="1605107979">QT实现背景图片多种填充方式:居中、平铺、缩放、拉伸_bochen_cwx的博客-CSDN博客</A>
                <DT><A HREF="https://blog.csdn.net/qq_25939803/article/details/100066444" ADD_DATE="1605107979">Python-PyQt5-背景图</A>
            </DL><p>
            <DT><A HREF="https://blog.csdn.net/zjkpy_5/article/details/89971191" ADD_DATE="1605107979">ModuleNotFoundError: No module named &#39;Crypto&#39;_木下瞳的博客-CSDN博客</A>
            <DT><A HREF="https://www.it610.com/article/1292321683318841344.htm" ADD_DATE="1605107979">9. PKI - 三种密钥交换算法详解(RSA&amp; DHE&amp; ECDHE)及他们在SSL/TLS协议中的应用 - it610.com</A>
        </DL><p>
    </DL><p>
</DL><p>