之前用异或算法自己加密文件,感觉密码还是太弱,在网上传输需要优秀的算法。于是用AES对称加密算法实现对电脑文件的加密。
实现主要有两个.py文件,一个是ecy.py,主要写了加密的核心算法。另一个是QT Desinger生成的界面文件,用信号与槽与核心算法链接。
一、算法文件
主要实现一个文件夹中文件的遍历,对每一个文件进行加解密操作,生成的新文件还放在该文件夹中。老文件可以删除,也可以保留。还可以选择是否对文件重新命名。下面是ecy.py文件。
from Crypto.Cipher import AES
import os
# 密钥(key), 密斯偏移量(iv) CBC模式加密
BLOCK_SIZE = 16 # Bytes
def pad(bs):
rbs=(BLOCK_SIZE - len(bs) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(bs) % BLOCK_SIZE)
rb=rbs.encode('utf8')
rb=bs+rb
return rb
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
vi = b'0102030405060708'
def encrypt_all(path1,key,flag,remove,rename):
"""
:param path1: 操作文件的路径
:param key: 密码
:param flag: 加解密标志。False位加密,True为解密
:param remove: False是不删除原文件,True删除源文件。
:param rename: False是添加前缀,True是不添加前缀。
:return:
"""
for root, dirs, files in os.walk(path1):
for name in files:
path3 = os.path.join(root, name)
if not flag:
name1 = "ej_" + name
else:
name1 = "dj_" + name
path4 = os.path.join(root, name1)
print(str(path3))
fin = open(path3, "rb+")
fou = open(path4, "ab+")
if not flag:
while True:
byt1 = fin.read(1023)
if not byt1:
break
byt2 = AES_Encrypt(key, byt1)
fou.write(byt2)
else:
while True:
byt1 = fin.read(1024)
if not byt1:
break
byt2 = AES_Decrypt(key, byt1)
fou.write(byt2)
fin.close()
fou.close()
if remove:
os.remove(path3)
if rename:
os.rename(path4, path3)
def make_key(key):
'''
密码必须是16字节的。如果小于16字节,如果小于16位,直接复制。如果大于16位,直接剪掉。
等下要考虑密码为空的情况。
'''
newKey = key.encode('utf8')
l = len(newKey)
while l<16:
newKey=newKey+newKey
l = len(newKey)
newKey=newKey[:16]
return newKey
def AES_Encrypt(key, data):
"""
:param key: 密码,二进制数据
:param data: 需加密的数据,二进制数据
:return: 已加密的二进制数据
"""
data = pad(data)
# 字符串补位
cipher = AES.new(key, AES.MODE_CBC, vi)
encryptedbytes = cipher.encrypt(data)
return encryptedbytes
def AES_Decrypt(key, data):
"""
:param key: 密码
:param data: 待解密的数据。这里必须使用经过加密的文件,好像加过密的文件,他有特殊的标识,
能够认出来。不是经这个程序加密的数据,会报错。
:return:解密后的数据。
"""
cipher = AES.new(key, AES.MODE_CBC, vi)
text_decrypted = cipher.decrypt(data)
# 去补位
text_decrypted = unpad(text_decrypted)
return text_decrypted
主要的两个函数是加密的AES_Encrypt(key, data)和解密的AES_Decrypt(key, data)两个函数。使用CBC模式对数据进行加解密。详细原理可参考Python 实现 AES 加密/解密.
其中AES_Encrypt(key, data)输入的字节数可以是任意的,他输出的加密数据不是任务的,而是比data字节数大,且最小的16倍的字节数。这里data取1023个字节,则输出的加密块就是1024个字节。
AES_Decrypt(key, data)解密函数必须输入的是经加密函数加密的数据,不然会报错。
二、界面文件
下面是主程序入口,也是界面文件main.py
import sys
import ecy
from PyQt5 import QtCore, QtGui, QtWidgets
flag_Pr=False#False是添加前缀,True是不添加前缀。
flag_Rm=False#False是不删除原文件,True删除源文件。
flag_Ec=False#False是加密,True是解密
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setGeometry(QtCore.QRect(100, 10, 581, 78))
self.label_3.setObjectName("label_3")
self.checkBox_2 = QtWidgets.QCheckBox(self.centralwidget)
self.checkBox_2.setGeometry(QtCore.QRect(440, 260, 171, 19))
self.checkBox_2.setObjectName("checkBox_2")
self.checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.checkBox.setGeometry(QtCore.QRect(240, 260, 111, 19))
self.checkBox.setObjectName("checkBox")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(80, 130, 120, 26))
self.label.setObjectName("label")
self.label_4 = QtWidgets.QLabel(self.centralwidget)
self.label_4.setGeometry(QtCore.QRect(90, 200, 98, 26))
self.label_4.setObjectName("label_4")
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setGeometry(QtCore.QRect(100, 260, 72, 26))
self.label_2.setObjectName("label_2")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(210, 130, 451, 24))
self.lineEdit.setObjectName("lineEdit")
self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit_2.setGeometry(QtCore.QRect(210, 200, 451, 24))
self.lineEdit_2.setObjectName("lineEdit_2")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(250, 400, 93, 28))
self.pushButton.setObjectName("pushButton")
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setGeometry(QtCore.QRect(420, 400, 93, 28))
self.pushButton_2.setObjectName("pushButton_2")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 27))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.checkBox.clicked['bool'].connect(self.removeOldFile)
self.checkBox_2.clicked['bool'].connect(self.hasProfix)
self.pushButton.clicked['bool'].connect(self.enCry)
self.pushButton_2.clicked['bool'].connect(self.deCry)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def removeOldFile(self):
global flag_Rm,flag_Pr
flag_Rm=not flag_Rm
if flag_Rm:
#删除源文件的情况
self.checkBox_2.setEnabled(True)
else:
self.checkBox_2.setEnabled(False)
self.checkBox_2.setCheckState(2)
flag_Pr=False
def hasProfix(self):
global flag_Pr,flag_Rm
flag_Pr = not flag_Pr
def enCry(self):
global flag_Pr, flag_Rm
lts=self.lineEdit.text()
if lts == "" or lts == "Please enter a Path!":
self.lineEdit.setText("Please enter a Path!")
else:
strs = lts.split("\\")
str = "\\\\".join(strs)
lts2 = self.lineEdit_2.text()
if lts2 == "" or lts2 == "Please enter a Key!":
self.lineEdit_2.setText("Please enter a Key!")
else:
newkey = ecy.make_key(lts2)
ecy.encrypt_all(str,newkey,False,flag_Rm,flag_Pr)
def deCry(self):
global flag_Pr, flag_Rm
lts = self.lineEdit.text()
if lts == "" or lts == "Please enter a Path!":
self.lineEdit.setText("Please enter a Path!")
else:
strs = lts.split("\\")
str = "\\\\".join(strs)
lts2 = self.lineEdit_2.text()
if lts2 == "" or lts2 == "Please enter a Key!":
self.lineEdit_2.setText("Please enter a Key!")
else:
newkey = ecy.make_key(lts2)
ecy.encrypt_all(str, newkey, True, flag_Rm, flag_Pr)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label_3.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">文件加解密程序</span></p></body></html>"))
self.checkBox_2.setText(_translate("MainWindow", "生成文件有前缀"))
self.checkBox_2.setEnabled(False)
self.checkBox_2.setCheckState(2)
self.checkBox.setText(_translate("MainWindow", "删除原文件"))
self.label.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:14pt; font-weight:600;\">文件路径:</span></p></body></html>"))
self.label_4.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:14pt; font-weight:600;\">密码:</span></p></body></html>"))
self.label_2.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:14pt; font-weight:600;\">选项:</span></p></body></html>"))
self.pushButton.setText(_translate("MainWindow", "加密"))
self.pushButton_2.setText(_translate("MainWindow", "解密"))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv) # 创建一个QApplication,即将开发的软件app
MainWindow = QtWidgets.QMainWindow() # QMainWindow装载需要的组件
ui = Ui_MainWindow()
ui.setupUi(MainWindow) # 执行类中的setupUi方法
MainWindow.show()
sys.exit(app.exec_()) # exit()或点击按钮退出app
该文件由QT Desinger自动生成,如何生成可参考python可视化开发,主要改写了4个槽函数,具体可参考代码。
三、意料之外的错误
在用pyinstaller进行发布的时候,出现了如下错误。
经过很长时间的尝试,感觉这个错误出引用的两个模块有冲突。单独有from PyQt5 import QtCore, QtGui, QtWidgets这个模块在的时候,运行没有问题。可以运行出UI解密。当引入from Crypto.Cipher import AES这个模块的时候,在python环境中运行也没有问题,但用pyinstaller发布出来会有冲突,没法运行,一运行就会报上面的错误。