程序简介
编程语言:python3.6源文件:aes_encode.py可执行程序:aes_encode.exe
程序功能
实现对小于1GB的任何格式的文件进行AES加密
设计思路
文件加密过程
要求使用者提供key(解密时用来验证身份)生成256bit随机字符串(python中为二进制)作为AES加密密钥将文件二进制流读入内存,并进行AES加密加密后的文件名称为“加密_”+原文件名将加密所用密钥以及使用者key和文件hash值等信息进行异或加密之后存入data.dat
文件解密过程
读取data.dat信息进入内存根据data.dat信息判断文件是否是加密文件。对用户解密时输入的key进行校对,若与加密时输入的key相同,则进行解密
使用说明
若有python的环境,可打开cmd窗口,定位到requirements.txt同级目录,运行
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
下载所需要的模块,然后双击py文件即可
或者直接运行exe文件第一次打开文件时,会要求设置初始密码,初始密码的作用为导出信息,至关重要,其存储路径为注册表中的位置
计算机\HKEY_CURRENT_USER\Software\bylyl\encode
设置完初始密码后会进入主界面首先点击选择文件或者在输入框中输入完整的程序路径以选择要加密的文件设置密码,用来在解密时验证用户的身份选择模式,有加密和解密两种,默认为加密点击确认加密
解密的时候操作与加密时候的操作类似,选择要解密的文件,输入加密时候的key,点击确认解密
注意
若忘记密码,可点击导出密码,在正确输入初始密码之后会在当前文件夹生成info.dat文件,用记事本打开,其格式为:
{
"加密文件hash":{
"primary_hash": "源文件hash",
"time": "加密时间",
"aes_key": "加密所用aes密钥",
"ensure_key": "加密时用户输入的key"
}
}
log.txt会记录对文件的操作过程data.dat是经过加密之后用来存储文件的重要信息,即info.dat的密文形式,若删除可能会造成无法解密
程序代码
import os
import hashlib
from cryptography
.fernet
import Fernet
import pickle
from datetime
import datetime
import functools
import time
import multiprocessing
import json
FILE_SIZE_HASH
= 4096
FILE_SIZE_ONCE_READ
= 1024*1024*2048
INFO_PATH
= os
.path
.join
(os
.path
.dirname
(__file__
), 'data.dat')
INFO_KEY
= 'madebylyl'
LOG_PATH
= os
.path
.join
(os
.path
.dirname
(__file__
), 'log.txt')
FILE_MAX_SIZE
= 1024*1024*1024
DECODE_INFO_PATH
= os
.path
.join
(os
.path
.dirname
(__file__
), 'info.dat')
def console_log(text
, path
=LOG_PATH
):
with open(path
, 'a') as f
:
f
.write
(datetime
.now
().strftime
("%Y-%m-%d %H-%M") + ':' + text
+ '\n')
def time_count(text
):
def decorator(function
):
@functools
.wraps
(function
)
def wrapper(*args
, **kwargs
):
text
= function
(*args
, **kwargs
)
first
= time
.time
()
end
= time
.time
()
console_log
("操作文件%s" % (args
[0]))
return text
return wrapper
return decorator
def get_file_hash(path
):
with open(path
, 'rb') as f
:
data
= f
.read
(FILE_SIZE_HASH
)
return hashlib
.sha256
(data
).hexdigest
()
def xor(de_data
, key
=INFO_KEY
):
count
= 0
en_data
= b
''
for byte
in de_data
:
new_byte
= byte
^ ord(key
[count
% len(key
)])
en_data
+= bytes([new_byte
])
count
+= 1
return en_data
def get_data(path
=INFO_PATH
, key
=INFO_KEY
):
data
= {}
if not os
.path
.exists
(INFO_PATH
):
print("We will create a file called 'data.dat' to storage infos, please remind not to delete it!")
with open(path
, 'wb') as f
:
f
.write
(xor
(pickle
.dumps
(data
)))
else:
with open(path
, 'rb') as f
:
data
= pickle
.loads
(xor
(f
.read
()))
return data
def get_decode_data(path
=DECODE_INFO_PATH
):
data
= get_data
()
with open(path
, 'w') as f
:
f
.write
(json
.dumps
(data
, indent
=2, ensure_ascii
=False))
class FileDispose:
def __init__(self
, path
, ensure_key
, data
, aes_key
="", file_new_name
=""):
self
.ensure_key
= ensure_key
self
.aes_key
= self
.get_key
(aes_key
)
self
.aes_f
= Fernet
(self
.aes_key
)
self
.base_path
, self
.file_name
= os
.path
.split
(path
)
self
.write_path
= self
.get_write_path
(file_new_name
)
self
.file_hash
= get_file_hash
(path
)
self
.file_datas
= self
.read_file
(path
)
self
.write_file
(data
)
def read_file(self
, path
):
with open(path
, 'rb') as f
:
while True:
data
= f
.read
(FILE_SIZE_ONCE_READ
)
if not data
:
break
else:
yield data
def get_key(self
, aes_key
):
if len(aes_key
) == 0:
self
.code
= 'encode'
return Fernet
.generate_key
()
else:
self
.code
= 'decode'
return aes_key
.encode
()
def encode(self
, de_data
):
return self
.aes_f
.encrypt
(de_data
)
def decode(self
, en_data
):
return self
.aes_f
.decrypt
(en_data
)
def write_file(self
, data
):
f
= open(self
.write_path
, 'wb')
for file_data
in self
.file_datas
:
code
= getattr(self
, self
.code
)
f
.write
(code
(file_data
))
f
.close
()
self
.new_file_hash
= get_file_hash
(self
.write_path
)
self
.write_info
(data
)
def write_info(self
, data
, info_path
=INFO_PATH
):
if self
.code
== "encode":
info
= {
self
.new_file_hash
:
{
'primary_hash': self
.file_hash
,
'time': datetime
.now
().strftime
("%Y-%m-%d %H-%M"),
'aes_key': self
.aes_key
.decode
(),
'ensure_key': self
.ensure_key
,
}
}
data
.update
(info
)
elif self
.code
== "decode":
data
.pop
(self
.file_hash
)
with open(info_path
, 'wb') as f
:
f
.write
(xor
(pickle
.dumps
(data
)))
def get_write_path(self
, file_new_name
):
if self
.code
== 'encode':
if len(file_new_name
) == 0:
new_name
= "加密_" + self
.file_name
return os
.path
.join
(self
.base_path
, new_name
)
else:
return os
.path
.join
(self
.base_path
, file_new_name
)
if self
.code
== 'decode':
if len(file_new_name
) == 0:
new_name
= "解密_" + self
.file_name
return os
.path
.join
(self
.base_path
, new_name
)
else:
return os
.path
.join
(self
.base_path
, file_new_name
)
@time_count
("加密")
def encode_file(path
, ensure_key
):
data
= get_data
()
file_hash
= get_file_hash
(path
)
if file_hash
in data
:
text
= "文件已经被加密!"
elif file_hash
in str(data
):
text
= "该文件已经存在加密版本"
else:
text
= "ok"
t
= multiprocessing
.Process
(target
=FileDispose
, args
=(path
, ensure_key
, data
))
t
.start
()
return text
@time_count
("解密")
def decode_file(path
, ensure_key
):
data
= get_data
()
file_hash
= get_file_hash
(path
)
if not file_hash
in data
:
text
= "该文件没有被加密"
else:
file_info
= data
.get
(file_hash
)
if ensure_key
!= file_info
.get
("ensure_key"):
text
= "密钥不正确"
else:
text
= "ok"
t
= multiprocessing
.Process
(target
=FileDispose
, args
=(path
, file_info
.get
("ensure_key"), data
, file_info
.get
("aes_key")))
t
.start
()
return text
import sys
from PyQt5
.QtWidgets
import QWidget
, QPushButton
, QComboBox
, QLabel
, QApplication
, QFileDialog
, QMessageBox
, QLineEdit
, QInputDialog
from PyQt5
.QtCore
import QSettings
class MyUI(QWidget
):
def __init__(self
):
super().__init__
()
self
.get_primary_key
()
self
.initUI
()
def get_primary_key(self
):
settings
= QSettings
("bylyl", "encode")
value
= settings
.value
("primary_key", '/')
if value
== "/":
self
.primary_key
= self
.init_key
()
else:
self
.primary_key
= value
def init_key(self
):
while True:
while True:
pwd1
, ok
= QInputDialog
.getText
(self
, "初始密码", "!!!请输入初始密码")
if not ok
:
sys
.exit
()
else:
if pwd1
== '' or pwd1
== '/':
self
.show_error
("密码不合法!")
else:
break
while True:
pwd2
, ok
= QInputDialog
.getText
(self
, "初始密码", "!!!请确认初始密码")
if ok
:
if pwd2
!= pwd1
:
self
.show_error
("两次输入密码不一致")
else:
settings
= QSettings
("bylyl", "encode")
settings
.setValue
("primary_key", pwd1
)
self
.show_info
("请牢记密码》{0}".format(pwd1
))
return pwd1
def initUI(self
):
self
.btn1
= QPushButton
('选择文件', self
)
self
.btn1
.move
(20, 20)
self
.btn1
.clicked
.connect
(self
.choose_file
)
self
.le
= QLineEdit
(self
, )
self
.le
.move
(130, 20)
self
.le
.resize
(350, 30)
self
.le
.setPlaceholderText
("输入文件完整路径或在左边选择文件")
self
.le1
= QLineEdit
(self
, )
self
.le1
.move
(130, 70)
self
.le1
.resize
(200, 30)
self
.le1
.setPlaceholderText
("请输入密码")
self
.le1
.setEchoMode
(self
.le1
.Password
)
self
.le2
= QLineEdit
(self
, )
self
.le2
.move
(130, 110)
self
.le2
.resize
(200, 30)
self
.le2
.setPlaceholderText
("确认密码")
self
.le2
.setEchoMode
(self
.le1
.Password
)
self
.info
= QLabel
(self
)
self
.info
.move
(60, 160)
self
.info
.setText
("<b>模式</b>")
self
.box
= QComboBox
(self
)
self
.box
.addItems
(("encode", "decode")),
self
.box
.move
(130, 155)
self
.box
.currentIndexChanged
.connect
(self
.change_mode
)
self
.btn2
= QPushButton
("确认加密",self
)
self
.btn2
.resize
(150,70)
self
.btn2
.move
(350, 80)
self
.btn2
.clicked
.connect
(self
.check_file
)
self
.btn3
= QPushButton
("导出密码", self
)
self
.btn3
.resize
(110,50)
self
.btn3
.move
(10, 74)
self
.btn3
.clicked
.connect
(self
.get_secret
)
self
.setGeometry
(500, 500, 600, 200)
self
.setWindowTitle
("加密软件:made_by_lyl")
self
.show
()
def choose_file(self
):
fname
= QFileDialog
.getOpenFileName
(self
, "选择文件", '/home')
if fname
[0]:
if os
.path
.getsize
(fname
[0]) > FILE_MAX_SIZE
:
self
.show_error
("当前只支持小于1GB文件")
else:
self
.le
.setText
(str(fname
[0]))
def change_mode(self
):
text
= self
.box
.currentText
()
if text
== "encode":
self
.btn2
.setText
("确认加密")
elif text
== "decode":
self
.btn2
.setText
("确认解密")
def check_file(self
):
mode
= self
.box
.currentText
()
path
= self
.le
.text
()
password1
= self
.le1
.text
().strip
()
password2
= self
.le2
.text
().strip
()
if len(password1
) == 0:
self
.show_error
("密码不能为空")
elif password1
!= password2
:
self
.show_error
("两次输入密码不一致")
elif len(path
) == 0:
self
.show_error
("请输入路径")
elif not os
.path
.isfile
(path
):
self
.show_error
("路径不合法")
elif os
.path
.getsize
(path
) > FILE_MAX_SIZE
:
self
.show_error
("当前只支持小于1GB文件")
self
.le
.clear
()
else:
if mode
== "encode":
text
= encode_file
(path
, password1
)
if text
.lower
() != "ok":
self
.show_error
(text
)
else:
self
.show_info
("已进入后台加密")
self
.show_im
("请牢记密码:{0}".format(password1
))
elif mode
== "decode":
text
= decode_file
(path
, password1
)
if text
.lower
() != "ok":
self
.show_error
(text
)
else:
self
.show_info
("已进入后台解密")
def get_secret(self
):
text
, ok
= QInputDialog
.getText
(self
, "导出密码", "请输入初始密码", QLineEdit
.Password
)
if ok
:
if text
== self
.primary_key
:
get_decode_data
()
self
.show_info
("请查看文件info.dat")
else:
self
.show_error
("密码错误")
def show_info(self
, text
):
QMessageBox
.information
(self
, "提示", text
)
def show_im(self
, text
):
QMessageBox
.critical
(self
, "重要", text
)
def show_error(self
, text
):
QMessageBox
.warning
(self
, "错误", text
)
def main():
multiprocessing
.freeze_support
()
try:
app
= QApplication
(sys
.argv
)
ex
= MyUI
()
sys
.exit
(app
.exec_
())
except Exception
as e
:
console_log
(e
)
if __name__
== '__main__':
main
()