(点击下面蓝色字体文字跳转到该链接)
1.0 版本的代码和注释,请到 自制密码管理器 —— 使用python、RSA加密文件 这里查看,对于你理解下面的代码会有帮助。
2.0 版本的效果如下面的视频:
视频演示链接:python密码管理器 2.0 版本
学习笔记
主要思路:
第一次登记管理员信息时,管理员输入密钥(一段中/英文本+数字/字母),代码将该输入的信息中的中/英文本进行哈希运算
(以下部分代码看不明白没关系,看注释,知道思路要做什么就行了,详细注释在文末)
print('\n\t【密钥格式】:(文本)(一个空格)(一段数字/字母)')
whatsay = (input('\t\t'+'可以说一句喜欢的话,再接几个数字/字母: ')).split(' ')
lenth = random.randint(9,16)
#获取随机字符的方法:先获取随机ASCII码,再转成字符型
beg,ex_1,ex_2 = random.randint(0,200),chr(random.randint(
48,57)),chr(random.randint(97,122))
new_rule = {'rule':{'beg':beg,'len':lenth,'ex_1':ex_1,'ex_2':ex_2}}
使用随机数截取哈希运算结果随机长度,作为AES的密钥,用于加密私钥,此处称AES密钥为key,这些随机的结果作为截取规则
def get_hash(text,rule,lenth=None):
m = hashlib.sha512()
m.update(text.encode('utf-8'))
pwd = m.hexdigest()
beg,lent = int(rule['beg']),int(rule['len'])
ex_1,ex_2 = rule['ex_1'],rule['ex_2']
lenth = lenth if lenth is not None else lent
pwd = pwd.replace(ex_1,ex_2) #字符替换
pwd = str(pwd)[beg:lenth+beg] #截取长度
return pwd
key = get_hash(whatsay[0],new_rule,lenth=None) #处理中/英文文本信息
接着代码使用输入信息中的数字/字母文本进行填充随机字符,补够16位作为AES的密钥,用于加密上述随机截取哈希结果字符串的截取规则,此处称AES密钥为ekey
def get_key(key,rule=1):
c = 'c'
if isinstance(rule,dict):
key = get_hash(key,rule)
c = rule['ex_2']
if len(key)<17:
key = key.ljust(16,c) #补充到16位
key = key.encode('utf-8')
return key
ekey = get_key(whatsay[1],rule=new_rule) #将数字/字母文本处理
随后,使用ekey加密截取规则,使用key加密私钥,使用生成的公钥将账号信息资料进行加密(为什么还要单独保存随机规则?为了后面验证身份的时候。仅仅提取出规则部分验证就行了,如果只放在数据里,就做不到验证的效果)
#super()表示父类的方法调用
super().get_key(whatsay[1]) #获取到ekey,加密随机截取规则
encrypt_ekey = str(super().aes_encrypt(str(new_rule)), encoding = "utf8")
super().lock_file_ekey(encrypt_ekey) #将密文保存到文件
'———————————————————————————————————————'
super().get_key(whatsay[0],new_rule['rule']) #获取到key,加密私钥
encrypt_priv = str(super().aes_encrypt(priv_key), encoding = "utf8")
super().lock_file_priv(encrypt_priv) #将密文保存到文件
'———————————————————————————————————————'
data.update(new_cipher)
data.update(new_rule)
self.save_lock_data(data) #将账户信息和加密规则一起加密保存到文件
将保存在密码文件的文本信息分组,截取规则放在第一部分,私钥放在第二部分,资料放在第三部分,下次登陆验证时,先提取出第一部分的密文,将用户输入的密钥分成两段,用第二段尝试去解密第一部分的截取规则,如果解密成功则表示密钥的数字/字母文本部分正确
接着将得到的规则(明文)、和第一段(用户输入的中英文本信息)放进哈希处理函数中,解开得到一段字符串(私钥),将该字符串用于去解密第三部分的资料,如果解密成功,则表示用户输入错误,否则报错返回
def check():
data = {}
str_ekey_encrypt = super().unlock_file_ekey() #读取密文的第一部分(关于截取规则的密文)
while True:
keys = input('\n'+'请输入密钥: ')
if keys=='q' or keys=='Q':
break
try:
super().get_key(keys.split(' ')[1])#将第一段用于解密第一部分的密文
str_ekey = super().aes_decrypt(str_ekey_encrypt) #如果报错则跳出try
dict_rule = eval(str_ekey)['rule']#如果解密成功,则得到的明文(截取规则)
str_priv_encrypt = super().unlock_file_priv()#读取密文的第二部分内容
super().get_key((keys.split(' ')[0]),dict_rule)#哈希处理得到key,用key去解密密文的第二部分(私钥)
priv_key = super().aes_decrypt(str_priv_encrypt)#如果解密成功,得到私钥的明文
str_data_encrypt = super().unlock_file_data()#读取密文的第三部分
dict_data = eval(super().rsa_decrypt((bytes(str_data_encrypt,encoding = "utf8"))))#尝试用私钥去解密,如果成功,则可以得到账户信息
admin = list(dict_data.keys())[0]
pub_key = dict_data[admin]['value']
return dict_data
except Exception as e:
print('\n'+'请输入正确的密钥!(输入【q】退出)')
到这里,就是整个加密解密的流程,保证了在密码文件中的数据永远是密文,只有在用户输入信息时才尝试解密,并且明文从不保存到文件中,相当于在线加密解密
'''
By Afeng
date:2020/02/29
virsion:2.0
'''
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
from Crypto.Signature import PKCS1_v1_5
import rsa,base64,hashlib
import re,os
from pathlib import Path
import random
class FileProcess(object): #关于文件的处理
def __init__(self):
self.disk_path = 'G:\\MyPwd\\'#密码文件保存文件夹路径
self.backup_path = 'E:\\MyPwd\\'#文件备份的文件夹路径
self.file_path = self.disk_path + 'key'
self.end_symbol = '!!@@##**&&^^%%$$##'#特殊符号,用于区分三部分信息
self.beg_symbol = '##$$%%^^&&**##@@!!'#截取规则、私钥、资料
self.cmd = False#是否新建管理员的命令标志
self.show_file()#使文件夹和文件可见可操作(如果隐藏,虽然在cmd可以找出,但是python不行)
if not Path(self.backup_path).is_dir():#如果找不到备份的文件夹
os.makedirs(self.backup_path)#新建文件夹
if not Path(self.disk_path).is_dir():#如果找不到文件的文件夹,则新建
print('\n'+'已创建新的文件夹!')
os.makedirs(self.disk_path)
if not Path(self.file_path).is_file():#如果密码文件不存在或者为空
if (input('\n'+'是否需要尝试找回密码文件?(Y/N): ')) == 'Y':
self.refind_pwd()#尝试从备份路径找回密码文件
if not Path(self.file_path).is_file():#再次检查是否已经找回
print('\n'+'找回密码失败!请手动查找。')
else:
print('\n'+'成功找回密码文件!')
else:
print('\n'+'密码文件为空!现在新建管理员账户:')
self.cmd = True#标志位为True,表示需要新建管理员信息
self.hide_file()#隐藏文件夹和文件
#隐藏文件夹,os.system("cmd")表示在windows上运行cmd命令
def hide_file(self):#这里的路径如果是'G:\\MyPwd\\',会操作失败,需要把后面两个\\去掉,变成'G:\\MyPwd'才行
os.system("attrib +H +R +S %s"%self.backup_path[:-1])
os.system("attrib +H +R +S %s"%self.disk_path[:-1])
def show_file(self):#显示文件夹和文件
os.system("attrib -H -R -S %s"%self.backup_path[:-1])
os.system("attrib -H -R -S %s"%self.disk_path[:-1])
def backup_file(self):#备份密码文件
os.system("ROBOCOPY %s %s /E /MT:10"%(self.disk_path,self.backup_path))
self.hide_file()
def refind_pwd(self):#找回密码文件
self.show_file()
os.system("ROBOCOPY %s %s /E /MT:10"%(self.backup_path,self.disk_path))
def new_file(self):#新建一个密码文件,并且备份
f = open(self.file_path,'w')
f.write('READY!')
f.close()
self.backup_file()
#密码文件中的密文(字符串)格式如下:
#规则密文+beg_symbol+私钥密文+end_symbol+资料
def update_file(self):#在写入资料时,先将规则和私钥拿出来,再覆盖写入,以删除原有的资料密文,以待写入新的资料密文
self.show_file()
f = open(self.file_path,'r')
str_encrypt = f.read()
#str.find()函数将找到的字符串下标返回(第一个)
str_priv_ekey = str_encrypt[:str_encrypt.find(self.end_symbol)]
f.close()
f = open(self.file_path,'w')
f.write(str_priv_ekey+self.end_symbol)
f.close()
def lock_file_data(self,str_encrypt):#保存资料的密文
self.update_file()
f = open(self.file_path,'a+')#追加模式,不覆盖原有信息
f.write(str_encrypt)
f.close()
self.backup_file()
def lock_file_priv(self,str_priv):#保存私钥的密文
self.show_file()
f = open(self.file_path,'a+')
f.write(str_priv+self.end_symbol)
f.close()
self.backup_file()
def lock_file_ekey(self,str_ekey):#保存规则的密文
self.show_file()
f = open(self.file_path,'w')
f.write(str_ekey+self.beg_symbol)
f.close()
self.backup_file()
def unlock_file_data(self):#读取资料的密文,返回密文(字符串型)
self.show_file()
f = open(self.file_path,'r')
str_encrypt = f.read()
f.close()
self.hide_file()
str_data_encrypt = str_encrypt[str_encrypt.find(self.end_symbol):]
str_data_encrypt.replace(self.end_symbol,'')
return str_data_encrypt
def unlock_file_priv(self):#读取私钥的密文,返回密文(字符串型)
self.show_file()
f = open(self.file_path,'r')
str_encrypt = f.read()
f.close()
beg,end = str_encrypt.find(self.beg_symbol),str_encrypt.find(self.end_symbol)
str_priv_encrypt = str_encrypt[beg:end].replace(self.beg_symbol,'')
self.hide_file()
return str_priv_encrypt
def unlock_file_ekey(self):#读取规则的密文,返回密文(字符串型)
self.show_file()
f = open(self.file_path,'r')
str_encrypt = f.read()
f.close()
str_ekey_encrypt = str_encrypt[:str_encrypt.find(self.beg_symbol)]
self.hide_file()
return str_ekey_encrypt
class PrpCrypt(object):#关于加密的操作,这部分代码的理解请看上一篇博文
def __init__(self):
self.key = None
self.mode = AES.MODE_CBC
self.pub_key = None
self.priv_key = None
def aes_encrypt(self, text):
text = text.encode('utf-8')
try:
cryptor = AES.new(self.key, self.mode, self.key)
length = 16
count = len(text)
if count < length:
add = (length - count)
text = text + ('\0' * add).encode('utf-8')
elif count > length:
add = (length - (count % length))
text = text + ('\0' * add).encode('utf-8')
self.ciphertext = cryptor.encrypt(text)
return b2a_hex(self.ciphertext)
except Exception as e:
print('\n'+'请输入正确的密钥!')
def aes_decrypt(self, text):
cryptor = AES.new(self.key, self.mode, self.key)
plain_text = cryptor.decrypt(a2b_hex(text))
return bytes.decode(plain_text).rstrip('\0')
def rsa_encrypt(self,plaintext, charset='utf-8'):
pubkey = RSA.importKey(base64.b64decode(self.pub_key))
pubkey_obj = Cipher_pkcs1_v1_5.new(pubkey)
encodetext = plaintext.encode(charset)
length = len(encodetext)
default_length = 117
res = []
for i in range(0, length, default_length):
res.append(pubkey_obj.encrypt(encodetext[i:i + default_length]))
byte_data = b''.join(res)
return base64.b64encode(byte_data)
def rsa_decrypt(self,ciphertext, sentinel=b'decrypt error'):
privkey = RSA.importKey(base64.b64decode(self.priv_key))
privkey_obj = Cipher_pkcs1_v1_5.new(privkey)
decodetext = base64.b64decode(ciphertext)
length = len(decodetext)
default_length = 128
res = []
for i in range(0, length, default_length):
res.append(privkey_obj.decrypt(decodetext[i:i + default_length], sentinel))
return str(b''.join(res), encoding = "utf-8")
def new_rsa(self):
pub_key, priv_key = rsa.newkeys(1024)
pub_key = pub_key.save_pkcs1().decode()
pub_key = pub_key.replace('-----BEGIN RSA PUBLIC KEY-----\n','')
self.pub_key = pub_key.replace('-----END RSA PUBLIC KEY-----\n','')
priv_key = priv_key.save_pkcs1().decode()
priv_key = priv_key.replace('-----BEGIN RSA PRIVATE KEY-----\n','')
self.priv_key = priv_key.replace('-----END RSA PRIVATE KEY-----\n','')
def get_key(self,key,rule=1):#得到key
c = 'c'
if isinstance(rule,dict):#如果传入的rule是字典,则使用随机字符
key = self.get_hash(key,rule)#在验证身份时的第二步用到
c = rule['ex_2']
if len(key)<17:
key = key.ljust(16,c)#左对齐补齐16位
self.key = key.encode('utf-8')
def get_hash(self,text,rule,lenth=None):#得到哈希值
m = hashlib.sha512()
m.update(text.encode('utf-8'))
pwd = m.hexdigest()
beg,lent = int(rule['beg']),int(rule['len'])
ex_1,ex_2 = rule['ex_1'],rule['ex_2']
lenth = lenth if lenth is not None else lent
pwd = pwd.replace(ex_1,ex_2)
pwd = str(pwd)[beg:lenth+beg]
return pwd
class Administrator(PrpCrypt,FileProcess):#关于管理员操作,继承了上面两个类
def __init__(self):
FileProcess.__init__(self)#继承属性
PrpCrypt.__init__(self)
if self.cmd:#子类可以使用父类里的属性
try:
self.new_admin()#如果标志位为True,新建管理员信息
except Exception as e:#如果新建过程失败,则删除文件
self.show_dir()#需要先使文件夹可见,不然os.remove()找不到
os.remove(self.file_path)
os.rmdir(self.disk_path)
self.hide_dir()
print('新建管理员失败!')
def new_admin(self):#新建管理员,密码文件夹为空时执行
data = {}
super().new_file()#子类使用父类的方法时,用super()
admin = (input('\n\t\t'+'管理员昵称: '))
print('\n\t'+'温馨提示:如果你记不住密钥,我保证你找不回密码!!')
super().new_rsa()#得到私钥公钥
print('\n\t【密钥格式】:(文本)(一个空格)(一段数字/字母)')
whatsay = (input('\t\t'+'可以说一句喜欢的话,再接几个数字/字母: ')).split(' ')
lenth = random.randint(9,16)
#获取随机字符的方法:先获取随机ASCII码,再转成字符型
beg,ex_1,ex_2 = random.randint(0,200),chr(random.randint(
48,57)),chr(random.randint(97,122))
new_rule = {'rule':{'beg':beg,'len':lenth,'ex_1':ex_1,'ex_2':ex_2}}
new_cipher = {admin:{'len':len(whatsay[0]),'value':self.pub_key,'e_key':whatsay[1]}}
'———————————————————————————————————————'
super().get_key(whatsay[1])#得到ekey,加密规则
encrypt_ekey = str(super().aes_encrypt(str(new_rule)), encoding = "utf8")
super().lock_file_ekey(encrypt_ekey)
'———————————————————————————————————————'
super().get_key(whatsay[0],new_rule['rule'])#得到key,加密私钥
encrypt_priv = str(super().aes_encrypt(self.priv_key), encoding = "utf8")
super().lock_file_priv(encrypt_priv)
'———————————————————————————————————————'
data.update(new_cipher)
data.update(new_rule)
self.save_lock_data(data)#使用上面的父类的公钥pub_key加密资料
'———————————————————————————————————————'
print('\n'+'管理员账户创建完成!请一定要记得密钥!')
def save_lock_data(self,dick_data):#加密资料
encrypt_data = str(super().rsa_encrypt(str(dick_data)), encoding = "utf8")
super().lock_file_data(encrypt_data)
def check(self):
data = {}
str_ekey_encrypt = super().unlock_file_ekey()
while True:
keys = input('\n'+'请输入密钥: ')
if keys=='q' or keys=='Q':
break
try:#注释请看上文
super().get_key(keys.split(' ')[1])
str_ekey = super().aes_decrypt(str_ekey_encrypt)
dict_rule = eval(str_ekey)['rule']
str_priv_encrypt = super().unlock_file_priv()
super().get_key((keys.split(' ')[0]),dict_rule)
self.priv_key = super().aes_decrypt(str_priv_encrypt)
str_data_encrypt = super().unlock_file_data()
dict_data = eval(super().rsa_decrypt((bytes(str_data_encrypt,encoding = "utf8"))))
admin = list(dict_data.keys())[0]
self.pub_key = dict_data[admin]['value']
return dict_data
except Exception as e:
print('\n'+'请输入正确的密钥!(输入【q】退出)')
def show_dir(self):
super().show_file()
def hide_dir(self):
super().hide_file()
#头好疼,写不下了,下面的代码应该不难看懂了,//凌晨1:08分
def add_count(self,count,data):
lenth = int(input('\n'+'请设置密码长度(键入【q】退出):'))
if lenth=='q' or lenth=='Q':
return
pwd = super().get_hash(count,data['rule'],lenth)
new_count = {count:{'len':lenth,'value':pwd}}
data.update(new_count)
self.save_lock_data(data)
print('\n'+'新账户密码保存成功!')
print(count,': ',data[count])
def change_name(self,count,data):
temp_key = data[count]['value']
temp_len = data[count]['len']
count = input('\n'+'请输入新的账户名(原有密码不改变):')
new_count = {count:{'len':temp_len,'value':temp_key}}
print('\n'+'账户名更改成功!(默认10位)')
data.update(new_count)
self.save_lock_data(data)
def change_pwd(self,count,data):
if (input('\n'+'是否需要初始化密码?(Y/N): ')) == 'Y':
new_count = {count:{'len':10,'value':(super().get_hash(count,data['rule']))}}
print('\n'+'密码初始化成功!(默认10位)')
else:
new_pwd = input('\n'+'请输入新的密码(键入【q】退出):')
if new_pwd=='q' or new_pwd=='Q':
return
lenth = len(new_pwd)
new_count = {count:{'len':lenth,'value':new_pwd}}
print('\n'+'新账户密码保存成功!')
data.update(new_count)
self.save_lock_data(data)
def allInfo(self,data):
for key,value in data.items():
print('\n'+key+": "+str(value)+'\n')
def del_count(self,key,data):
if key in data:
data.pop(key)
self.save_lock_data(data)
print('\n'+'已删除 %s 的账户密码!'%key)
if __name__ == '__main__':
admin = Administrator()
while True:
count = input('\n'+'请输入要查询的账号: ')
data = admin.check()
if data:
if count in data.keys():
print(count,': ',data[count])
cmd = input('\n'+'【-c】:更改账户名\t【-p】:更改密码\t键入其他值退出:')
if cmd=='-c' or cmd=='-C':
admin.change_name(count,data)
elif cmd=='-p' or cmd=='-P':
admin.change_pwd(count,data)
elif count == 'ls':
print(list(data.keys())[2:])
admin.show_dir()
cmd = input('\n'+'【-a】:查看全部密码\t【-d】:删除账户\t键入其他值退出:')
if cmd=='-a' or cmd=='-A':
data = admin.check()
admin.allInfo(data)
elif cmd=='-d' or cmd=='-D':
temp = input('\n'+'输入账号名,否则输入任意字符退出: ')
admin.del_count(temp,data)
admin.hide_dir()
else:
if (input('\n'+'是否需要保存新账号?(Y/N): ')) == 'Y':
admin.add_count(count,data)
else:
print('\n'+'文件为空!')
仅作python学习实践,如有错误,还望指正!
加油!