自制密码管理器 —— 使用python、RSA加密文件(加强版)

  
  
  (点击下面蓝色字体文字跳转到该链接)
  
  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学习实践,如有错误,还望指正!
  加油!

发布了17 篇原创文章 · 获赞 33 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/fengge2018/article/details/104602231