Windows版微信加密存储图片的解密工具GUI程序

 

 

 

摘要

摘要:本文主要对PC版微信的图片存储加密方式进行了研究,通过探讨图片文件中文件头信息中保存的文件标识字符数据得过程,得出了解密还原为正常图片的方式。并采用UI与业务逻辑分离的模式设计了对非程序员用户友好的GUI界面和完整的解密逻辑设计。

关键词:Python; 解密; 微信; PyQt5;

 

1.        引言

Windows版微信中,其会把用户收发的图片加密存储为dat文件,用户无法直接打开dat图片文件。而微信加密存储的dat文件本质是由普通图片文件(如jpg/bmp/png)格式中的二进制数据逐位与随机加密码进行异或运算得到的。计算出正确的加密数字,并对dat文件作异或运算即可还原为可正常打开的普通图片文件。本项目基于以上思路,使用Python语言进行解密逻辑程序设计,使用PyQt等模块,设计出对非程序员友好且便于操作的解密工具GUI程序。

2.        系统结构

该程序设计采用UI与业务逻辑分离的模式进行设计。因此实现系统结构介绍分为逻辑实现设计介绍和UI界面设计介绍。

2.1.系统总体设计

该程序设计采用开发平台为PyCharm,采用程序语言为Python。所使用的Python模块为sys模块、os模块、threading模块、PyQt模块。不同的Python模块分别用于实现不同的程序功能

 

表1 模块-功能对照表

模块名称

程序功能

sys, os模块

系统相关操作模块与文件、文件目录相关操作模块,用于实现相关文件操作。

threadin模块

多线程操作模块,实现UI界面与业务逻辑分离操作,并实现多线程解密操作,可能加快程序运行。

PyQt模块

Qt库与Python融合的模块。用于程序主要UI设计,实现UI操作功能。

 

该程序设计的目的是做出对非程序员用户友好的且方便操作的界面,因此,在该系统中,GUI界面为主要模块,通过GUI界面相关操作调用业务模块实现功能。业务模块完成相关操作后,将输出结果返回给GUI模块进行展示。

 

图1 程序GUI界面

 

图2 程序模块关系

 

2.2.逻辑实现设计

在PC版微信图片缓存文件夹(一般为

“C:\Users\用户名\Documents\WeChat Files\微信号\FileStorage\Image\xxxx-xx“)中,存储大量的dat格式文件,这些为微信对聊天中收发到的图片进行二次加密处理后得到的文件。

图3 微信图片缓存文件夹中的dat文件

 

使用16进制的方式打开这些文件可以看到前两个字节为“0xDF, 0xF8”。与jpg图片格式头信息的“0xFF, 0xD8”不符。对图片字节数据直接修改的加密方式易于联想到“异或法加密”。

 

图4 以十六进制打开dat文件

 

用0xDF与0xFF做异或运算,0xF8与0xD8做异或运算。结果为0x20。对多个dat格式文件进行计算后,均得到相同结果。可得结论加密码为0x20。但在实际操作中,每个用户的加密码都不一样,需要在程序中计算出的加密码。

图5 异或运算结果

 

要解密dat文件,首先要知道dat文件在加密前的文件格式。图片的格式很多,一个图片文件的后缀名并不能说明这个图片的真正格式什么,可以通过读取图片文件的文件头标识得到。因为各种格式的图片的文件头标识不同的,因此可以通过判断文件头的标识来识别图片格式。

 

这里以Windows位图(bmp)格式作为参考例子:BMP(Bitmap-File)图形文件是Windows采用的图形文件格式,位图文件可看成由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、彩色表(color table)和定义位图的字节阵列。 在这里,我们只关心其图像文件头信息即可,图像文件头信息结构如下表:

 

表2 bmp位图文件头信息

偏移量

域的名称

大小

内容

0000h

文件标识

2 bytes

识别位图的类型

0002h

File Size

1 dword

用字节表示的整个文件的大小

0006h

Reserved

1 dword

保留,必须设置为0

000Ah

Bitmap Date Offset

1 dword

从文件开始到位图数据开始之间的数据(bitmap data)之间的偏移量

000Eh

Bitmap Header Size

1 dword

位图信息头(Bitmap Info Header)的长度

0012h

Width

1 dword

位图的宽度

0016h

Height

1 dword

位图的高度

001Ah

Planes

1 word

 

位图的位面数

 

从表2可以看到在图片格式头信息中,文件标识为信息的前两个字节。为了获得图片格式,这里只需关心文件标识,查阅资料可得其它格式的文件标识如表3所示。

 

表3 图片格式与头信息对照表

图片格式

头信息前两个字节(16进制)

jpg

0xff, 0xd8

bmp

0x42, 0x4d

png

0x89, 0x50

gif

0x47, 0x49

 

基于上述解密算法思路和信息,进行逻辑实现。步骤如下:

1.     读取dat文件前两个字节的数据。

2.     根据表3所列图片格式的头文件信息,逐个遍历,用其第一个字节与dat文件读取出来的第一个字节数据进行 异或运算。 得到加密码。

3.     使用加密码与dat文件第二个字节数据进行异或运算,得到校验码。

4.     校验码与表3所列图片格式的第二个字节进行校验。

5.     校验通过,则表示加密码正确,使用正确的加密码对整个dat文件的数据进行运算,获取正确的图片数据。

6.     将正确的图片数据写入新创建的文件。解密完成。

图6 解密流程图

 

2.3UI界面设计

该程序中的UI设计部分使用PyQt中的designer工具进行设计。

图7 程序主界面

 

所用控件介绍:

1.     label标签,用于提示用户输入

2.     lineedit控件,用于获取用户输入文件夹路径

3.     button按钮,用于实现用户点击调用功能

4.     checkbox控件,用于实现功能选用

5.     textedit控件,用于将内容输出展示给用户

6.     processbar控件,用于展示解密任务处理进度

 

在UI实现中,主要采用信号与槽机制实现UI与功能交互。信号与槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。

 

在该程序中,为防止解密过程线程任务过于繁忙,导致UI界面无法进行相应。采用主线程运行UI界面,子线程运行解密程序的方式运行整个程序。

 

UI程序的运行逻辑程序框图如下:

图8 程序GUI逻辑流程图

 

3.        实现代码

该程序设计采用UI与业务逻辑分离的模式进行设计。因此实现代码分为UI代码和逻辑实现代码。

在代码中,注释已经对代码块进行充分的解释。

3.1.逻辑实现代码

import sys, os, threading

from wechatdecode_ui import Ui_MainWindow

from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox

from PyQt5.QtGui import QIcon

 

from time import time

import inspect

import ctypes

 

class WechatMainWindow(QMainWindow):

    def __init__(self):

        super().__init__()

        self.ui = Ui_MainWindow()

        self.ui.setupUi(self)

        # 标识符属性

        self.start_flag = False

        self.finish_flag = False

        # 统计用属性

        self.end_num = 0

        self.decode_code = 0

        self.file_all_num = 0

        self.file_done_num = 0

        self.file_fail_num = 0

        # 多线程列表

        self.threads = []

        # 统计时间变量

        self.start_time = 0

        self.end_time = 0

        # 信号与槽链接

        self.ui.pic_src_button.clicked.connect(self.onclick_src_button)

        self.ui.pic_out_button.clicked.connect(self.onclick_out_button)

        self.ui.start_button.clicked.connect(self.onclick_start_button)

        self.ui.about_button.clicked.connect(self.show_about_dialog)

        self.ui.open_out_button.clicked.connect(self.onclick_open_out_button)

        self.ui.open_src_button.clicked.connect(self.onclick_open_src_button)

        self.ui.close_button.clicked.connect(self.close)

        # 控件初始化

        self.ui.text_out_textEdit.append("微信加密图片默认存储于\nC:\\Users\\您的用户名\\Documents\\WeChat "

                                         "Files\\您的微信号\\FileStorage\\Image\\20XX-XX\\")

        self.ui.text_out_textEdit.append('\n若默认存储文件夹不存在,请查看\"微信程序->设置->文件管理\"中的设置')

       

    # 重写关闭事件,用于关闭确认

    def closeEvent(self, event):

        if self.start_flag:

            return_code = QMessageBox.warning(self, "确认关闭", '解密程序正在运行,是否确认要关闭?', QMessageBox.Yes | QMessageBox.No)

            if return_code == QMessageBox.Yes:

                for t in self.threads:

                    stop_thread(t)

                    event.accept()

            else:

                event.ignore()

 

    # 槽

    def show_about_dialog(self):

        '''

        用于展示关于窗口

        :return:

        '''

        QMessageBox.about(self, "关于", '本软件适用于最新版PC微信 V2.9.5.33'

                                      '\n理论上兼容以往版本'

                                      '\n\n本软件仅供学习交流,如作他用所承受的法律责任一概与作者无关'

                                      '\n\n该软件可能存在未知原因崩溃问题,请勿作正规用途'

                                      '\n\n下载使用即代表您同意上述观点'

                                      '\n\n版权 © 2020 Leon. 版权所有,翻录必究。'

                                      '\nCopyright © 2020 Leon. All rights reserved.')

 

    def onclick_open_out_button(self):

        '''

        用系统文件管理器打开路径

        :return:

        '''

        path = self.ui.pic_out_lineEdit.text()

        if not os.path.exists(path):

            QMessageBox.critical(self, "文件夹打开错误", "请在输出目录处输入正确的文件夹路径")

        else:

            os.startfile(path)

 

    def onclick_open_src_button(self):

        '''

        用系统文件管理器打开路径

        :return:

        '''

        path = self.ui.pic_src_lineEdit.text()

        if not os.path.exists(path):

            QMessageBox.critical(self, "文件夹打开错误", "请在微信加密图片目录处输入正确的文件夹路径")

        else:

            os.startfile(path)

 

    def run_time(self):

        '''

        用于实时数据输出到textEdit并更新processbar进度条

        :return:

        '''

        if self.file_done_num < self.file_all_num:

            self.ui.progressBar.setValue(self.file_done_num)

        elif not self.finish_flag:

            self.end_time = time()

            self.finish_flag = True

            self.start_flag = False

            self.ui.progressBar.setValue(self.file_done_num)

            self.ui.text_out_textEdit.append("解密完成,共解密 %d 个文件,成功 %d 个,失败 %d 个" % (

                self.file_all_num, self.file_all_num - self.file_fail_num, self.file_fail_num))

            self.ui.text_out_textEdit.append("用时 %d 秒" % (self.end_time - self.start_time))

            self.ui.text_out_textEdit.moveCursor(self.ui.text_out_textEdit.textCursor().End)

            if self.ui.done_after_open_checkbox.checkState() == 2:

                # 完成后打开文件夹

                path = self.ui.pic_out_lineEdit.text()

                if not os.path.exists(path):

                    pass

                else:

                    os.startfile(path)

            # 重新初始化

            self.ui.mul_thread_checkbox.setDisabled(False)

            self.ui.pic_src_button.setDisabled(False)

            self.ui.pic_out_button.setDisabled(False)

            self.ui.pic_src_lineEdit.setDisabled(False)

            self.ui.pic_out_lineEdit.setDisabled(False)

            self.ui.start_button.setText("开始(&S)")

 

    def onclick_src_button(self):

        '''

        浏览按钮打开文件浏览器,默认路径为微信文件夹

        :return:

        '''

        user_path = os.path.expanduser('~')

        wechat_path = user_path + '\\Documents\\WeChat Files'

 

        choose_path = QFileDialog.getExistingDirectory(self, '选择PC版微信图片存储文件夹', wechat_path)

        choose_path = choose_path.replace('/', '\\')

        if choose_path == '':

            choose_path = self.ui.pic_src_lineEdit.text()

        self.ui.pic_src_lineEdit.setText(choose_path)

 

    def onclick_out_button(self):

        '''

        浏览按钮打开文件浏览器,默认路径为图片文件夹

        :return:

        '''

        user_path = os.path.expanduser('~')

        output_path = user_path + '\\Pictures'

        choose_path = QFileDialog.getExistingDirectory(self, '选择解密后图片存储文件夹', output_path)

        choose_path = choose_path.replace('/', '\\')

        if choose_path == '':

            choose_path = self.ui.pic_out_lineEdit.text()

        self.ui.pic_out_lineEdit.setText(choose_path)

       

    # 开始

    def onclick_start_button(self):

        '''

        开始按钮,执行读取文件目录文件、多线程调用等功能

        :return:

        '''

        if not self.start_flag:

            self.ui.text_out_textEdit.textChanged.connect(self.run_time)

            self.start_time = 0

            self.start_time = time()

            dir_path = self.ui.pic_src_lineEdit.text()

            save_path = self.ui.pic_out_lineEdit.text()

            # 判断文件是否存在

            if not os.path.exists(dir_path) or not os.path.exists(save_path):

                QMessageBox.critical(self, "文件夹目录错误", "请设置正确的微信图片目录或图片保存目录")

            else:

                # 获取目录下文件列表

                files_list = os.listdir(dir_path)

                self.file_all_num = len(files_list)

                # 初始化操作

                self.file_done_num = 0

                self.file_fail_num = 0

                self.ui.progressBar.setValue(0)

                self.ui.progressBar.setMaximum(self.file_all_num)

                self.ui.text_out_textEdit.clear()

                self.start_flag = True

                self.finish_flag = False

                self.ui.mul_thread_checkbox.setDisabled(True)

                self.ui.pic_src_button.setDisabled(True)

                self.ui.pic_out_button.setDisabled(True)

                self.ui.pic_src_lineEdit.setDisabled(True)

                self.ui.pic_out_lineEdit.setDisabled(True)

                self.ui.start_button.setText("取消(&S)")

                # 多进程设置

                self.threads.clear()

                if self.ui.mul_thread_checkbox.checkState() == 2:

                    self.end_num = len(files_list) // 2

                    t2 = threading.Thread(target=self.decode_dat, args=(files_list[self.end_num:], "线程2"))

                    self.threads.append(t2)

                else:

                    self.end_num = len(files_list)

                t1 = threading.Thread(target=self.decode_dat, args=(files_list[:self.end_num], "线程1"))

                self.threads.append(t1)

 

                for t in self.threads:

                    # s设置守护进程

                    t1.setDaemon(True)

                    t.start()

        else:

            return_code = QMessageBox.warning(self, "确认取消", '解密程序正在运行,是否确认要取消?', QMessageBox.Yes | QMessageBox.No)

            if not return_code == QMessageBox.Yes:

                pass

            else:

                # 取消后解密关闭子进程

                for t in self.threads:

                    stop_thread(t)

                self.start_flag = False

                self.finish_flag = True

                self.ui.mul_thread_checkbox.setDisabled(False)

                self.ui.pic_src_button.setDisabled(False)

                self.ui.pic_out_button.setDisabled(False)

                self.ui.pic_src_lineEdit.setDisabled(False)

                self.ui.pic_out_lineEdit.setDisabled(False)

                self.ui.start_button.setText("开始(&S)")

               

    # 核心功能

    def decode_dat(self, files_list, str):

        '''

        解密功能核心,解密并生成正确二代图片文件

        :param files_list: 获取到微信dat文件目录下的文件

        :param str: 进程名称

        :return:

        '''

        # 文件信息头前两个字节

        # jpg / png / gif格式 / bmp格式

        pic_head = [0xff, 0xd8, 0x89, 0x50, 0x47, 0x49, 0x42, 0x4D]

        cant_decode_flag = False

        # 遍历读取文件

        for file_name in files_list:

            file_path = os.path.join(self.ui.pic_src_lineEdit.text(), file_name)

            # 判断文件是否dat后缀

            if not file_path.endswith(".dat"):

                self.ui.text_out_textEdit.append(

                    "<font color=\"#FF0000\">%s</font>" % (str + " " + file_path + " 解密失败,"

                                                           + "失败原因:非dat格式文件"))

                self.file_fail_num = self.file_fail_num + 1

                self.file_done_num = self.file_done_num + 1

                continue

            with open(file_path, "rb") as dat_file:

                dat_read = dat_file.read(2)

                if len(dat_read) == 0:

                    self.ui.text_out_textEdit.append(

                        "<font color=\"#FF0000\">%s</font>" % (str + " " + file_path + " 解密失败,"

                                                               + "失败原因:读取文件失败"))

                    self.file_fail_num = self.file_fail_num + 1

                    self.file_done_num = self.file_done_num + 1

                    continue

                head_index = 0

                cant_decode_flag = False

                # 开始解密

                while head_index < len(pic_head):

                    # 使用第一个头信息字节来计算加密码

                    # 第二个字节来验证解密码是否正确

                    code = dat_read[0] ^ pic_head[head_index]

                    idf_code = dat_read[1] ^ code

                    head_index = head_index + 1

                    if idf_code == pic_head[head_index]:

                        self.decode_code = code

                        break

                    else:

                        cant_decode_flag = True

 

                    head_index = head_index + 1

            # 根据索引得到文件后缀

            if 0 <= head_index <= 1:

                exten_name = ".jpg"

            elif 2 <= head_index <= 3:

                exten_name = ".png"

            elif 4 <= head_index <= 5:

                exten_name = ".gif"

            elif 5 <= head_index <= 6:

                exten_name = ".bmp"

            elif cant_decode_flag:

                self.ui.text_out_textEdit.append("<font color=\"#FF0000\">%s</font>" % (str + " " + file_path + "解密失败,"

                                                                                        + "失败原因:未找到相应图片格式"))

                self.file_fail_num = self.file_fail_num + 1

                self.file_done_num = self.file_done_num + 1

                continue

 

            pic_name = os.path.join(self.ui.pic_out_lineEdit.text(), file_name)

            pic_name = pic_name.split('.')[0]

            pic_name = pic_name + exten_name

            # 对dat文件所有数据进行解密,并生成正确图片文件

            try:

                dat_file = open(file_path, "rb")

                pic_write = open(pic_name, "wb")

                for dat_data in dat_file:

                    for dat_byte in dat_data:

                        pic_data = dat_byte ^ self.decode_code

                        pic_write.write(bytes([pic_data]))

            except OSError:

                self.ui.text_out_textEdit.append("<font color=\"#FF0000\">%s</font>" % (str + " " + file_path + "解密失败,"

                                                                                        + "失败原因:系统错误"))

                self.file_fail_num = self.file_fail_num + 1

                self.file_done_num = self.file_done_num + 1

            except IOError:

                self.ui.text_out_textEdit.append("<font color=\"#FF0000\">%s</font>" % (str + " " + file_path + "解密失败,"

                                                                                        + "失败原因:写入或读取错误"))

                self.file_fail_num = self.file_fail_num + 1

                self.file_done_num = self.file_done_num + 1

            else:

                self.ui.text_out_textEdit.append(str + " " + pic_name + " " + "解密完成")

                self.file_done_num = self.file_done_num + 1

            finally:

                self.ui.text_out_textEdit.moveCursor(self.ui.text_out_textEdit.textCursor().End)

                dat_file.close()

                pic_write.close()

 

 

# 子进程强制性停止函数,该实现参考于博客

https://www.cnblogs.com/rainduck/archive/2013/03/29/2989810.html

def _async_raise(tid, exctype):

    '''

    :param tid:子进程进程号

    :param exctype:执行类型

    :return:无

    '''

    tid = ctypes.c_long(tid)

    if not inspect.isclass(exctype):

        exctype = type(exctype)

    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))

    if res == 0:

        raise ValueError("invalid thread id")

    elif res != 1:

        # """if it returns a number greater than one, you're in trouble,

        # and you should call it again with exc=NULL to revert the effect"""

        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)

        raise SystemError("PyThreadState_SetAsyncExc failed")

 

 

def stop_thread(thread):

    '''

    :param thread:子进程对象

    :return:无

    '''

    _async_raise(thread.ident, SystemExit)

 

 

if __name__ == '__main__':

    app = QApplication(sys.argv)

    window = WechatMainWindow()

    window.setWindowTitle("PC版微信图片解密工具 Beta1")

    app.setWindowIcon(QIcon('./lemon.ico'))

    window.show()

    QMessageBox.information(window, "提示", "该软件仅用于学习研究,可能存在未知原因崩溃问题,请勿作正规用途。")

    exit(app.exec_())

3.2.  UI代码

# -*- coding: utf-8 -*-

 

# Form implementation generated from reading ui file 'wechatdecode_ui.ui'

#

# Created by: PyQt5 UI code generator 5.13.2

#

# WARNING! All changes made in this file will be lost!

 

 

from PyQt5 import QtCore, QtGui, QtWidgets

 

 

class Ui_MainWindow(object):

    def setupUi(self, MainWindow):

        # 初始化

        MainWindow.setObjectName("MainWindow")

        MainWindow.setEnabled(True)

        MainWindow.resize(829, 519)

        MainWindow.setMinimumSize(QtCore.QSize(800, 480))

        MainWindow.setMaximumSize(QtCore.QSize(1280, 720))

        # 主要使用表格布局,并将各控件添加进表格布局中合适的位置

        self.centralwidget = QtWidgets.QWidget(MainWindow)

        self.centralwidget.setObjectName("centralwidget")

        self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)

        self.horizontalLayout.setObjectName("horizontalLayout")

        self.gridLayout = QtWidgets.QGridLayout()

        self.gridLayout.setObjectName("gridLayout")

        self.pic_out_label = QtWidgets.QLabel(self.centralwidget)

        self.pic_out_label.setObjectName("pic_out_label")

        self.gridLayout.addWidget(self.pic_out_label, 2, 0, 1, 1)

        self.pic_out_lineEdit = QtWidgets.QLineEdit(self.centralwidget)

        self.pic_out_lineEdit.setObjectName("pic_out_lineEdit")

        self.gridLayout.addWidget(self.pic_out_lineEdit, 2, 1, 1, 3)

        self.close_button = QtWidgets.QPushButton(self.centralwidget)

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)

        sizePolicy.setHorizontalStretch(0)

        sizePolicy.setVerticalStretch(0)

        sizePolicy.setHeightForWidth(self.close_button.sizePolicy().hasHeightForWidth())

        self.close_button.setSizePolicy(sizePolicy)

        self.close_button.setObjectName("close_button")

        self.gridLayout.addWidget(self.close_button, 7, 4, 1, 1)

        self.pic_src_button = QtWidgets.QPushButton(self.centralwidget)

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)

        sizePolicy.setHorizontalStretch(0)

        sizePolicy.setVerticalStretch(0)

        sizePolicy.setHeightForWidth(self.pic_src_button.sizePolicy().hasHeightForWidth())

        self.pic_src_button.setSizePolicy(sizePolicy)

        self.pic_src_button.setObjectName("pic_src_button")

        self.gridLayout.addWidget(self.pic_src_button, 1, 4, 1, 1)

        self.start_button = QtWidgets.QPushButton(self.centralwidget)

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)

        sizePolicy.setHorizontalStretch(0)

        sizePolicy.setVerticalStretch(0)

        sizePolicy.setHeightForWidth(self.start_button.sizePolicy().hasHeightForWidth())

        self.start_button.setSizePolicy(sizePolicy)

        self.start_button.setObjectName("start_button")

        self.gridLayout.addWidget(self.start_button, 7, 5, 1, 1)

        self.pic_src_lineEdit = QtWidgets.QLineEdit(self.centralwidget)

        self.pic_src_lineEdit.setObjectName("pic_src_lineEdit")

        self.gridLayout.addWidget(self.pic_src_lineEdit, 1, 1, 1, 3)

        self.pic_out_button = QtWidgets.QPushButton(self.centralwidget)

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)

        sizePolicy.setHorizontalStretch(0)

        sizePolicy.setVerticalStretch(0)

        sizePolicy.setHeightForWidth(self.pic_out_button.sizePolicy().hasHeightForWidth())

        self.pic_out_button.setSizePolicy(sizePolicy)

        self.pic_out_button.setObjectName("pic_out_button")

        self.gridLayout.addWidget(self.pic_out_button, 2, 4, 1, 1)

        self.about_button = QtWidgets.QPushButton(self.centralwidget)

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)

        sizePolicy.setHorizontalStretch(0)

        sizePolicy.setVerticalStretch(0)

        sizePolicy.setHeightForWidth(self.about_button.sizePolicy().hasHeightForWidth())

        self.about_button.setSizePolicy(sizePolicy)

        self.about_button.setObjectName("about_button")

        self.gridLayout.addWidget(self.about_button, 7, 0, 1, 1)

        self.open_src_button = QtWidgets.QPushButton(self.centralwidget)

        self.open_src_button.setObjectName("open_src_button")

        self.gridLayout.addWidget(self.open_src_button, 1, 5, 1, 1)

        self.text_out_textEdit = QtWidgets.QTextEdit(self.centralwidget)

        self.text_out_textEdit.setEnabled(True)

        self.text_out_textEdit.setReadOnly(True)

        self.text_out_textEdit.setObjectName("text_out_textEdit")

        self.gridLayout.addWidget(self.text_out_textEdit, 4, 0, 2, 6)

        self.open_out_button = QtWidgets.QPushButton(self.centralwidget)

        self.open_out_button.setObjectName("open_out_button")

        self.gridLayout.addWidget(self.open_out_button, 2, 5, 1, 1)

        self.progressBar = QtWidgets.QProgressBar(self.centralwidget)

        self.progressBar.setMinimumSize(QtCore.QSize(0, 1))

        self.progressBar.setProperty("value", 0)

        self.progressBar.setObjectName("progressBar")

        self.gridLayout.addWidget(self.progressBar, 6, 0, 1, 6)

        self.pic_src_label = QtWidgets.QLabel(self.centralwidget)

        self.pic_src_label.setObjectName("pic_src_label")

        self.gridLayout.addWidget(self.pic_src_label, 1, 0, 1, 1)

        self.mul_thread_checkbox = QtWidgets.QCheckBox(self.centralwidget)

        self.mul_thread_checkbox.setObjectName("mul_thread_checkbox")

        self.gridLayout.addWidget(self.mul_thread_checkbox, 3, 0, 1, 1)

        self.done_after_open_checkbox = QtWidgets.QCheckBox(self.centralwidget)

        self.done_after_open_checkbox.setChecked(True)

        self.done_after_open_checkbox.setObjectName("done_after_open_checkbox")

        self.gridLayout.addWidget(self.done_after_open_checkbox, 3, 1, 1, 1)

        self.horizontalLayout.addLayout(self.gridLayout)

        MainWindow.setCentralWidget(self.centralwidget)

        self.menubar = QtWidgets.QMenuBar(MainWindow)

        self.menubar.setGeometry(QtCore.QRect(0, 0, 829, 22))

        self.menubar.setObjectName("menubar")

        MainWindow.setMenuBar(self.menubar)

        self.statusbar = QtWidgets.QStatusBar(MainWindow)

        self.statusbar.setObjectName("statusbar")

        MainWindow.setStatusBar(self.statusbar)

        self.pic_out_label.setBuddy(self.pic_out_lineEdit)

        self.pic_src_label.setBuddy(self.pic_src_lineEdit)

 

        self.retranslateUi(MainWindow)

        QtCore.QMetaObject.connectSlotsByName(MainWindow)

 

    def retranslateUi(self, MainWindow):

        _translate = QtCore.QCoreApplication.translate

        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))

        # 设置控件提示符或设置控件字符

        self.pic_out_label.setToolTip(_translate("MainWindow", "<html><head/><body><p>设置解密后图片保存目录</p></body></html>"))

        self.pic_out_label.setText(_translate("MainWindow", "解密后图片保存目录:"))

        self.pic_out_lineEdit.setToolTip(_translate("MainWindow", "<html><head/><body><p>设置解密后图片保存目录</p></body></html>"))

        self.close_button.setText(_translate("MainWindow", "关闭(&C)"))

        self.pic_src_button.setToolTip(_translate("MainWindow", "<html><head/><body><p>微信加密图片默认存储于</p><p><span style=\" text-decoration: underline;\">&quot;C:\\Users\\您的用户名\\Documents\\WeChat Files\\您的微信号\\FileStorage\\Image\\20XX-XX&quot;</span></p></body></html>"))

        self.pic_src_button.setText(_translate("MainWindow", "浏览.."))

        self.start_button.setText(_translate("MainWindow", "开始(&S)"))

        self.pic_src_lineEdit.setToolTip(_translate("MainWindow", "<html><head/><body><p>微信加密图片默认存储于</p><p><span style=\" text-decoration: underline;\">&quot;C:\\Users\\您的用户名\\Documents\\WeChat Files\\您的微信号\\FileStorage\\Image\\20XX-XX&quot;</span></p></body></html>"))

        self.pic_out_button.setToolTip(_translate("MainWindow", "<html><head/><body><p>设置解密后图片保存目录</p></body></html>"))

        self.pic_out_button.setText(_translate("MainWindow", "浏览.."))

        self.about_button.setText(_translate("MainWindow", "关于(&A)"))

        self.open_src_button.setText(_translate("MainWindow", "打开"))

        self.open_out_button.setText(_translate("MainWindow", "打开"))

        self.pic_src_label.setToolTip(_translate("MainWindow", "<html><head/><body><p>微信加密图片默认存储于</p><p><span style=\" text-decoration: underline;\">&quot;C:\\Users\\您的用户名\\Documents\\WeChat Files\\您的微信号\\FileStorage\\Image\\20XX-XX&quot;</span></p></body></html>"))

        self.pic_src_label.setText(_translate("MainWindow", "微信图片的缓存目录:"))

        self.mul_thread_checkbox.setToolTip(_translate("MainWindow", "<html><head/><body><p>启用双线程可能会加快解密速度,但也会占用过多的CPU资源</p></body></html>"))

        self.mul_thread_checkbox.setText(_translate("MainWindow", "启动双线程"))

        self.done_after_open_checkbox.setText(_translate("MainWindow", "完成后打开文件夹"))

4.        实验

图9 程序运行主界面

 

图10 程序解密运行时

 

图11 防止用户误操作

 

图12 程序关于页面

 

图12 解密前的dat文件

 

图13 解密后的生成的可正常识别的图片

 

5.        总结与展望

本次程序设计,旨在以软件开发者的身份出发,为用户考虑需求,做出对普通用户友好、易用的图形化解密小工具。通过这次设计对Python的文件读写操作有了更深的理解和认识,对异常捕捉更为熟悉。对异或法加密文件的方式也有了新的理解。同时也学会了PyQt的使用。也初步尝试了Python的多线程编程。但尽管做了防止用户误操作的措施和文件读写时可能出来的异常进行捕捉,该程序仍有低概率会在解密过程时出现崩溃。可能是因为我对多线程编程尚未熟悉,操作失当导致。未来会力求排查出原因并持续对该程序进行维护改进。

6.        参考文献

[1] python中threading方式创建的线程的终止

https://www.cnblogs.com/rainduck/archive/2013/03/29/2989810.html

[2] BMP文件格式详解

https://blog.csdn.net/o_sun_o/article/details/8351037

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值