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

  
  

视频演示链接:用python做的密码管理器
  
  

1.前言

  自从迷上各种网站以后,各种注册压根停不下来,密码老是记不住是接触互联网的人都会遇到的问题。
  有的人不管是什么密码,都统一用相同的密码,省去了不必要的麻烦,但是如果某天随意一个账号密码泄露,坏人来入侵你简直易如反掌。(只要知道你手机号和一个密码,就可以去尝试登陆不同的网站,并不是什么平台都会有短信验证码这种安全操作的)
  有的人会使用网上的密码管理器,看似安全,但是假如不法分子在软件上故意留了后门,那岂不是很危险。大数据时代,防人之心不可无啊!
  这几天在学习python语言,倒不如来练练手,自己写一个简易的密码管理器,密码的规则只有你知道,设置不同符号就可以挡住暴力破解,每一个账号的密码都不相同,都不简单。

2.需求

 ① 输入不同账户名,就可以生成不同的可设置长度的密码;
 ② 也能够自己修改密码,并且更新密码文件;
 ③ 不想记密码,要有保存密码的文件,以后可以查看;
 ④ 密码文件不轻易被别人看到,能够加密、自动备份、隐藏。
(大神请绕路,不要尝试找我的漏洞,因为你一定找得到~小小百姓能够使用就可以啦,本文旨在分享学习心得)

3.问题思路

  把账户名用哈希计算,得到固定长度的数据,比如SHA-512算法得到512位长度的数据,此过程不可逆。然后自行设置规则 —— 提取该数据的某一段、什么字符替换什么字符等,这个规则自行设定且要牢记。得到某一长度的密码后,连同账户名一起写入到文件中,这里可以使用python的字典来保存账号密码,对于后续多账号的管理操作会比较简单。然后使用RSA非对称加密来加密密码文件里的信息,在需要读取文件时,再用私钥去解密即可读取出文件内容。
关键点1:如果担心规则记不住,可以在提示信息里加以备注;
关键点2:RSA加解密中必须考虑到的密钥长度、明文长度和密文长度问题,对于1024长度的密钥,128字节(1024bits)-减去11字节正好是117字节,可以先对数据进行bas64加密, 再对加密后的内容使用rsa加密, 最后对rsa解密后的内容进行bas64解密。
关键点3:关于公钥密钥的保存,可以在代码里嵌入在线获取公钥密钥的方法(有点危险),也可以先把公钥密钥保存为pem文件放在本地,在代码里调用出公钥,只有在自己修改查看密码时将私钥文件放进特定路径再使用(安全系数更高)。
关键点4:每一次对密码文件的读写,都要经过加密、解密、备份的过程。加密过程是先读取文件里所有文本,经过加密再保存到文件里;解密过程是先读取文件的密文,经过解密后再将信息保存到文件里,对于文件的隐藏和备份,使用os.system()执行cmd命令。

4.开始办事

环境:WIN7 32位  python3.8.1
需要用到的包:hashlib,rsa,base64,os,re(注意有些自带了)
可以使用镜像源下载:pip install -i http://pypi.douban.com/simple --trusted-host pypi.douban.com hashlib(需要下载哪个包,就把黄色名字改成包名)
查看是否有某个工具包的方法:进入cmd,先输入python,再输入import+包名,如果有这个工具包,则不会报错。

文件操作
1、open() 方法
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True)
参数 用途
file 必需,文件路径(相对或者绝对路径)
mode 可选,文件打开模式
模式 描述
r 以只读方式打开文件,这是默认模式
w 打开一个文件只用于写入,如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。

2、file.read()方法
read() 方法用于从文件读取指定的字节数,如果未给定或为负则读取所有

3、file.readlines()方法
readlines() 方法用于读取所有行(直到结束符 EOF)并返回列表,该列表可以由 Python 的 for… in … 结构进行处理。 如果碰到结束符 EOF 则返回空字符串。

4、file.write()方法
write() 方法用于向文件中写入指定字符串。如果文件打开模式带 b,那写入文件内容时,str (参数)要用 encode 方法转为 bytes 形式,否则报错:TypeError: a bytes-like object is required, not ‘str’。

示例:

在F:\MyPwd\b.txt下,输入如下内容:

123
456
789
{123}

新建一个.py文件,然后运行代码

file_path = "F:\\MyPwd\\b.txt" #使用绝对路径
f = open(file_path,'r')
read_data = f.read()
readlines_data = f.readlines()
f.close() #记得一定要关了文件
print('read_data:\n{}\ntype: {}\n\n'.format(read_data,type(read_data)))
print('readlines_data:\n{}\ntype: {}'.format(readlines_data,type(readlines_data)))
f = open(file_path,'w')
f.write('abcd')
f.close()

运行结果为:

readlines_data:  #使用readlines读取文件,返回列表
['123\n', '456\n', '789\n', '{123}']
type: <class 'list'>

read_data:  #使用read读取文件,返回字符串
123
456
789
{123}
type: <class 'str'>

abcd  #使用write写文件,将删除原有内容,再写入

鉴于此,先对密码文件的储存格式规划,在代码里,希望通过字典的方式来保存、调用信息,键值为账号名称,其值里再包含密码长度、密码内容,示例如下:

data = {
     '张三': {'len': '13', 'value': '1aswedwxdsaaa'},
     '李四': {'len': '12', 'value': '1aswedwxdsaa'},
     '小五': {'len': '11', 'value': '1aswedwxdsa'}
     }

使用write()函数时,传入参数不能直接将字典传进去,否则报错:TypeError: write() argument must be str, not dict,所以可以规定,将键值存放在单数行,值存放在双数行

f = open(file_path,'w') #file_path和data在前文已定义
for k,v in data.items():#data.items()返回字典的某一项内容
    #k为键值,写入在文件的单数行,如'张三'
    #v为value,如{'len': '13', 'value': '1aswedwxdsaaa'}
    f.write(str(k)+'\n') #主键值写完,回车
    #x为键值,如'len'  y为该键值对应的value,如'13'
    for x,y in v.items(): #同理操作
        f.write(str(x)+' ') #以空格隔开,方便后续分离
        f.write(str(y)+' ')
    f.write('\n') #次键值写完,回车
f.close()

运行结果如下,在txt文件可以看到:

张三  #主键值
len 13 value 1aswedwxdsaaa #以空格分开
李四
len 12 value 1aswedwxdsaa 
小五
len 11 value 1aswedwxdsa 
  #最后一行是回车

现在已经完成将字典内容写入txt文件,内容是以字符串形式储存的,接下来是如何读取出txt文件的字符串,再转成字典格式呢?回忆上文的readlines()函数,使用它读取文件返回的是list类型数据

f = open(file_path,'r')
data = f.readlines()
print(data)
f.close()

返回的结果如下

['张三\n', 'len 13 value 1aswedwxdsaaa \n', '李四\n', 'len 12 value 1aswedwxdsaa \n', '小五\n', 'len 11 value 1aswedwxdsa \n']

返回的是列表,列表的每一项内容是字符串,因此可以接着对这个返回结果操作,将有用信息提取出来

print('原字典信息:\n{}\n\n'.format(data))#file_path和data在前文已定义

f = open(file_path,'r')
information = f.readlines()
name,lenth,value = [],[],[] #分别存放姓名、密码长度、密码
data = {} #新建空白字典
for i in range(len(information)): #遍历所有信息条
    if i % 2 == 0:  # for循环从0开始,因此对应的是文件的第一行,即键值   
       s1 = information[i].replace('\n', '')#将字符串中的'\n'替换,即删除
       name.append(s1) #添加到name列表
    else: #对应的是主键值的value,如'len 13 value 1aswedwxdsaaa \n'
       s2 = information[i].split()  #将该字符串以空格分开组成新的列表,如['len', '13', 'value', '1aswedwxdsaaa']
       lenth.append(s2[1]) #index=1对应长度的数值
       value.append(s2[3]) #index=3对应密码的数值
for j in range(len(name)): #在所有账号中遍历
    #新建字典,格式如下:
    temp = {name[j]:{'len':lenth[j],'value':value[j]}}
    data.update(temp) #在data字典里增加新的内容
f.close()
print('读取出的信息:\n{}\n\n'.format(data))

运行结果如下:

原字典信息:
{'张三': {'len': '13', 'value': '1aswedwxdsaaa'}, '李四': {'len': '12', 'value': '1aswedwxdsaa'}, '小五': {'len': '11', 'value': '1aswedwxdsa'}}

读取出的信息:
{'张三': {'len': '13', 'value': '1aswedwxdsaaa'}, '李四': {'len': '12', 'value': '1aswedwxdsaa'}, '小五': {'len': '11', 'value': '1aswedwxdsa'}}

到这里就可以实现,将字典写入文件,并且能从文件读出数据组成字典。值得注意的是,每次写入字典,都会覆盖原有内容,因此要使用dict.update()形式来增加字典内容,保证数据的完整。

加密操作
  RSA算法是现今使用最广泛的公钥密码算法,也是号称地球上最安全的加密算法。在了解RSA算法之前,先熟悉下几个术语 根据密钥的使用方法,可以将密码分为对称密码和公钥密码 对称密码:加密和解密使用同一种密钥的方式 公钥密码:加密和解密使用不同的密码的方式,因此公钥密码通常也称为非对称密码。 关于RSA的原理,可以看这篇文章http://www.guideep.com/read?guide=5676830073815040

  简单的理解就是,使用公钥加密的数据,只有私钥才能解开,公钥可以给需要传信息给你的人,让他加密,你再用私钥解开,加密的密文不是固定的,但是用私钥解开,得到的数据是固定的。
  加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘
密密钥)SK是需要保密的。RSA密钥至少为500位长,一般推荐使用1024位。RSA密钥长度随着保密级别提高,增加很快。由于RSA的特性,一个1024位的密钥只能加密117位字节数据,当数据量超过117位字节的时候,程序就会抛出异常。

1、生成公私钥对方法

import rsa

# 1、接收者(A)生成512位公私钥对
# a. lemon_pub为PublicKey对象, lemon_priv为PrivateKey对象
# b. 512为秘钥的位数, 可以自定义指定, 例如: 128、256、512、1024、2048等
pubkey, privkey = rsa.newkeys(1024)

print('公钥:\n{}'.format(pubkey))
print('私钥:\n{}'.format(privkey))

运行结果为:

公钥:
PublicKey(136043793246195131376598853070437698306114833972959095011027114725856352489960372449126256960893386324011025727709760353355161658861524305075813113012558114176160175699651050556689236540303816483879189644670406301771660213339983982106027499836359428406193843275085402340802395917935876461206122900474103550081, 65537)
私钥:
PrivateKey(136043793246195131376598853070437698306114833972959095011027114725856352489960372449126256960893386324011025727709760353355161658861524305075813113012558114176160175699651050556689236540303816483879189644670406301771660213339983982106027499836359428406193843275085402340802395917935876461206122900474103550081, 65537, 67111644348222967139256312003406484676391848609880945751354297863602787372025250488735399660431255319213214852325503947754281954178450047806598354045723899698083205408652913843867050341620368862896794412934853121165578244016362213359581410238054965306729656494042087397877482874513921173331474467024976125353, 49941274253535477245884116206745790020259530246041428907030691911368756042123890501630166746194845032347468161709234004166916134481594454244519551020710967997946663, 2724075332069930615036147089954740192436418597964199394724831470277180639582666198461658504095140678009881992486078909443511818268222744093220887)

2、发送者加密方法

# 2、发送者(B)使用接收者(A)的公钥去加密消息
# rsa只能处理字节类型, 故字符串类型需要转化为字节类型
love_talk = "little girl, I love you very much!".encode("utf-8")
cryto_info = rsa.encrypt(love_talk, pubkey) # 使用接收者(A)的公钥加密

print(cryto_info)

运行结果为:(bytes型数据)

b"1\x06\xa6\x89\x8b\xd7\xfb\xc7=\xe6\x0b\x7f\x03\xceqp\x19$l\x86\x9e\x1e\xee\xd2\xb0b\xedK\xb9\xd9\xc8\x83\xf2\x8c~\xe0\xebP\xd0\x04\xbb\x9f\x1f\xb9O\x95\x1c\xf7\xfbK\xf29\xff\x7f\xbb\x0e\xb2\x99\x89\xed*\xdf\xd2\x84K#\xdb\x14r$'\x03d!{&z\xf4\x0cQcc\xcb\x96\xa7\xcb\x010\x90^O`\xe4\xb9\x0fY&L8i\x8d\x8b\xcf\x86\x00\x80\xbb\t\xee8\xae1\xb4\xa0O\x12_-\xda\xf1\x05\x0b\xf7\xd5Y\xed\xf6c"

3、接收者解密方法

# 3. 接收者(A)使用自己的私钥去解密消息
talk_real = rsa.decrypt(cryto_info, privkey)
talk_real2 = talk_real.decode("utf-8")
print(talk_real2)

解密结果为:

little girl, I love you very much!

4、保存密钥方法

with open('F:\\MyPwd\\rsa\\public.pem' ,'w+') as f:
    f.write(pubkey.save_pkcs1().decode())
with open('F:\\MyPwd\\rsa\\private.pem' ,'w+') as f:
    f.write(privkey.save_pkcs1().decode())

打开private.pem文件可以看到的结果如下,和上面直接打印出的密钥有所不同,这是因为写入文件时,需要将数据进行解码

-----BEGIN RSA PRIVATE KEY-----
MIICYAIBAAKBgQCD051f1t6GplBhOIMfx8uGpzq3A73vqx76oxJ14MROTqcunatD
EzmRLPgELWDufrt8Hv0ZQ3GYC3g4Ozlk0ukhWVQFaFbtNyu4HhnzEnnZ4FQAerjG
CsbSaPtJNPq1Dgql1RQiIC0uSEuYvvFckI7eyH6hOpvqySCtXKRdRdnQ0QIDAQAB
AoGAAIXiZfLwRxB52SjkPEgKoqofLYKySjUfllb3R8hwfu8I8sJlX4q/+7d19G5J
qCiQjdmBn4wI81V4UKDLhMeYzsKoaWzLUaURCFkv7nH7p4nv0nU/fIdx2KymzBYt
iOz/mQ1NPLc4vZ0PJ0ncPQrVbCavYs2H4d/8o5cDvrI8eAECRQC0dsNc875HK8y4
acebaOjwHGX4Ap+bLYKL5P8zfgHLLV+putVcx5N4AnRyrLNjfzYS0FZHXhAFgCOz
QmR/KkorcXNIoQI9ALsBOQgiZiWO3GEnMQqnM6qhmZg9727rWLd+/AvhxFs7Yy4Q
e1Tpa4U66R4FljYBOcuCfGIDNvIcL86qMQJECKTDmLkn/PqxFIgkgmIU/iMuEyH1
CRa18QNn4cyAQ34J3fRP8eCxRIdBkpiJAxP9wArwhvyPYeQQUa61Z43b/ZaygeEC
PECUI4XTm0LNGv3R8vWi2AzM0aXpfY3oaDK1/4R66rw2vgFiX7TrBt5zgZ2EgGMV
+Ud2QE34njjt0vSjgQJFAI3XEB2gqY/g4LbgCQpbaWlaNA/w8EPvNEe2SFlsKJBU
uWe6l78I/sbvdRLfJ12XIN4I3Jnl8C3gwMI9iLaUz2MxuiUe
-----END RSA PRIVATE KEY-----

5、导出密钥方法

f = open('F:\\MyPwd\\rsa\\public.pem','r')
pubkey = f.read().replace('-----BEGIN RSA PUBLIC KEY-----\n','')
pubkey = pubkey.replace('-----END RSA PUBLIC KEY-----\n','')
f.close()
f = open('F:\\MyPwd\\rsa\\private.pem','r')
privkey = f.read().replace('-----BEGIN RSA PRIVATE KEY-----\n','')
privkey = privkey.replace('-----END RSA PRIVATE KEY-----\n','')
f.close()

print('pubkey:\n{}'.format(pubkey))
print('privkey:\n{}'.format(privkey))

导出结果为:可以发现导出的结果和在文件里看到的是一样的

pubkey:
MIGJAoGBAIPTnV/W3oamUGE4gx/Hy4anOrcDve+rHvqjEnXgxE5Opy6dq0MTOZEs
+AQtYO5+u3we/RlDcZgLeDg7OWTS6SFZVAVoVu03K7geGfMSedngVAB6uMYKxtJo
+0k0+rUOCqXVFCIgLS5IS5i+8VyQjt7IfqE6m+rJIK1cpF1F2dDRAgMBAAE=

privkey:
MIICYAIBAAKBgQCD051f1t6GplBhOIMfx8uGpzq3A73vqx76oxJ14MROTqcunatD
EzmRLPgELWDufrt8Hv0ZQ3GYC3g4Ozlk0ukhWVQFaFbtNyu4HhnzEnnZ4FQAerjG
CsbSaPtJNPq1Dgql1RQiIC0uSEuYvvFckI7eyH6hOpvqySCtXKRdRdnQ0QIDAQAB
AoGAAIXiZfLwRxB52SjkPEgKoqofLYKySjUfllb3R8hwfu8I8sJlX4q/+7d19G5J
qCiQjdmBn4wI81V4UKDLhMeYzsKoaWzLUaURCFkv7nH7p4nv0nU/fIdx2KymzBYt
iOz/mQ1NPLc4vZ0PJ0ncPQrVbCavYs2H4d/8o5cDvrI8eAECRQC0dsNc875HK8y4
acebaOjwHGX4Ap+bLYKL5P8zfgHLLV+putVcx5N4AnRyrLNjfzYS0FZHXhAFgCOz
QmR/KkorcXNIoQI9ALsBOQgiZiWO3GEnMQqnM6qhmZg9727rWLd+/AvhxFs7Yy4Q
e1Tpa4U66R4FljYBOcuCfGIDNvIcL86qMQJECKTDmLkn/PqxFIgkgmIU/iMuEyH1
CRa18QNn4cyAQ34J3fRP8eCxRIdBkpiJAxP9wArwhvyPYeQQUa61Z43b/ZaygeEC
PECUI4XTm0LNGv3R8vWi2AzM0aXpfY3oaDK1/4R66rw2vgFiX7TrBt5zgZ2EgGMV
+Ud2QE34njjt0vSjgQJFAI3XEB2gqY/g4LbgCQpbaWlaNA/w8EPvNEe2SFlsKJBU
uWe6l78I/sbvdRLfJ12XIN4I3Jnl8C3gwMI9iLaUz2MxuiUe

6、加密长字符串方法
  加密的字段长短规则如下:
  加密的 plaintext 最大长度是 证书key位数/8 - 11, 例如1024 bit的证书,被加密的串最长 1024/8 - 11=117,那么对于 2048bit的证书,被加密的长度最长2048/8 - 11 =245,解决办法是 分块 加密,然后分块解密就行了,因为 证书key固定的情况下,加密出来的串长度是固定的。也就是说,如果使用2048bit的证书,并且被加密的字符段是小于245个,那么被加密出来的字符长度是344个,以此类推,被加密的字符串可以是688个,1032个等。

import rsa
pubkey, privkey = rsa.newkeys(1024)
data = ‘little girl, I love you very much!’*100
love_talk = data.encode(“utf-8”)
cryto_info = rsa.encrypt(love_talk, pubkey)

报错信息:

raise OverflowError(’%i bytes needed for message, but there is only’
OverflowError: 3400 bytes needed for message, but there is only space for 117

解决方法如下,可以加密足够长的字符串:

import rsa
import base64
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
from Crypto.Signature import PKCS1_v1_5
from Crypto.PublicKey import RSA

#密钥一定要经过解码,否则无法直接使用,即bytes转str
pubkey = '''
        MIGJAoGBAIPTnV/W3oamUGE4gx/Hy4anOrcDve+rHvqjEnXgxE5Opy6dq0MTOZEs
        +AQtYO5+u3we/RlDcZgLeDg7OWTS6SFZVAVoVu03K7geGfMSedngVAB6uMYKxtJo
        +0k0+rUOCqXVFCIgLS5IS5i+8VyQjt7IfqE6m+rJIK1cpF1F2dDRAgMBAAE=
        '''
privkey = '''
        MIICYAIBAAKBgQCD051f1t6GplBhOIMfx8uGpzq3A73vqx76oxJ14MROTqcunatD
        EzmRLPgELWDufrt8Hv0ZQ3GYC3g4Ozlk0ukhWVQFaFbtNyu4HhnzEnnZ4FQAerjG
        CsbSaPtJNPq1Dgql1RQiIC0uSEuYvvFckI7eyH6hOpvqySCtXKRdRdnQ0QIDAQAB
        AoGAAIXiZfLwRxB52SjkPEgKoqofLYKySjUfllb3R8hwfu8I8sJlX4q/+7d19G5J
        qCiQjdmBn4wI81V4UKDLhMeYzsKoaWzLUaURCFkv7nH7p4nv0nU/fIdx2KymzBYt
        iOz/mQ1NPLc4vZ0PJ0ncPQrVbCavYs2H4d/8o5cDvrI8eAECRQC0dsNc875HK8y4
        acebaOjwHGX4Ap+bLYKL5P8zfgHLLV+putVcx5N4AnRyrLNjfzYS0FZHXhAFgCOz
        QmR/KkorcXNIoQI9ALsBOQgiZiWO3GEnMQqnM6qhmZg9727rWLd+/AvhxFs7Yy4Q
        e1Tpa4U66R4FljYBOcuCfGIDNvIcL86qMQJECKTDmLkn/PqxFIgkgmIU/iMuEyH1
        CRa18QNn4cyAQ34J3fRP8eCxRIdBkpiJAxP9wArwhvyPYeQQUa61Z43b/ZaygeEC
        PECUI4XTm0LNGv3R8vWi2AzM0aXpfY3oaDK1/4R66rw2vgFiX7TrBt5zgZ2EgGMV
        +Ud2QE34njjt0vSjgQJFAI3XEB2gqY/g4LbgCQpbaWlaNA/w8EPvNEe2SFlsKJBU
        uWe6l78I/sbvdRLfJ12XIN4I3Jnl8C3gwMI9iLaUz2MxuiUe
        '''

def public_long_encrypt(data, charset='utf-8'):
    global pubkey
    # base64.b64decode()解码一个字符串
    pub_key = RSA.importKey(base64.b64decode(pubkey))# 导入公钥
    pub_key_obj = Cipher_pkcs1_v1_5.new(pub_key)
    data = data.encode(charset) #将数据进行编码
    length = len(data)
    default_length = 117
    res = []
    for i in range(0, length, default_length):
        res.append(pub_key_obj.encrypt(data[i:i + default_length]))
    byte_data = b''.join(res)
    return base64.b64encode(byte_data)

def private_long_decrypt(data, sentinel=b'decrypt error'):
    global privkey
    pri_key = RSA.importKey(base64.b64decode(privkey))
    pri_key_obj = Cipher_pkcs1_v1_5.new(pri_key)
    data = base64.b64decode(data) # 将数据进行解码
    length = len(data)
    default_length = 128
    res = []
    for i in range(0, length, default_length):
        res.append(pri_key_obj.decrypt(data[i:i + default_length], sentinel))
    return str(b''.join(res), encoding = "utf-8")

data = 'little girl, I love you very much!'*100

cryto_info = public_long_encrypt(data)
print('加密信息:\n{}'.format(cryto_info))

decrypt_info = private_long_decrypt(cryto_info)
print('解密信息:\n{}'.format(decrypt_info))

运行结果如下:

到这里,就可以实现对文本进行加密解密了,主要思路就是通过读取文件得到字符串,对长字符串进行rsa加密,再将密文写入回文件里,以后即使比人看到文件,也无法知道其内容是什么,直到我们想用的时候再解密即可。

5.全部代码

'''
By Afeng
date:2020/02/28
virsion:1.0

'''

import os
import hashlib
import rsa
import base64
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
from Crypto.Signature import PKCS1_v1_5
from Crypto.PublicKey import RSA
import re

initval = {
     '测试1': {'len': '3', 'value': '1'},
     '2': {'len': '2', 'value': '2'},
     '1': {'len': '1', 'value': '3'}
     }

#如果有关于路径的错误,这里最好用绝对路径
public_path = 'F:\\MyPwd\\public.pem'
private_path = 'F:\\MyPwd\\private.pem'
file_path = 'F:\\MyPwd\\a'
path = 'F:\\MyPwd'
target_path = 'G:\\MyPwd'

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
                            加密相关                           
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

# 函数功能:将账号进行哈希运算,得到lenth长度
# 入口参数:账户名、密码长度
# 返回值:经过密码规则处理后的密码
def hash_pwd(value,lenth):
    m = hashlib.sha512()
    m.update(value.encode('utf-8'))#直接加密字符串会报错
    pwd = m.hexdigest() #得到512位数据
    pwd = pwd.replace('c','&') #将字符串里的'c'符号替换成'&'
    pwd = str(pwd)[3:lenth+3] #取lenth长度的数据
    return pwd

# 函数功能:生成新的公钥密钥并且保存到本地
# 入口参数:无
# 返回值:无
def new_keys():
    global public_path,private_path
    pub_key, priv_key = rsa.newkeys(1024)
    with open(public_path ,'w+') as f:
        f.write(pub_key.save_pkcs1().decode())
    with open(private_path ,'w+') as f:
        f.write(priv_key.save_pkcs1().decode())

# 函数功能:从本地文件导入密钥
# 入口参数:无
# 返回值:无        
def import_keys():
    global public_path,private_path,pubkey,privkey
    f = open(public_path,'r')
    pubkey = f.read().replace('-----BEGIN RSA PUBLIC KEY-----\n','')#去掉前面后面不必要的数据
    pubkey = pubkey.replace('-----END RSA PUBLIC KEY-----\n','')
    f.close()
    f = open(private_path,'r')
    privkey = f.read().replace('-----BEGIN RSA PRIVATE KEY-----\n','')
    privkey = privkey.replace('-----END RSA PRIVATE KEY-----\n','')
    f.close()
    
# 函数功能:加密长字符串
# 入口参数:需要加密的字符串
# 返回值:返回b64编码的数据,即密文
def public_long_encrypt(data, charset='utf-8'):
    global pubkey
    pub_key = RSA.importKey(base64.b64decode(pubkey))
    pub_key_obj = Cipher_pkcs1_v1_5.new(pub_key)
    data = data.encode(charset)
    length = len(data)
    default_length = 117
    res = []
    for i in range(0, length, default_length):
        res.append(pub_key_obj.encrypt(data[i:i + default_length]))
    byte_data = b''.join(res)
    return base64.b64encode(byte_data)
    
# 函数功能:解密长字符串
# 入口参数:需要解密的b64编码数据
# 返回值:返字符串
def private_long_decrypt(data, sentinel=b'decrypt error'):
    global privkey
    pri_key = RSA.importKey(base64.b64decode(privkey))
    pri_key_obj = Cipher_pkcs1_v1_5.new(pri_key)
    data = base64.b64decode(data)
    length = len(data)
    default_length = 128
    res = []
    for i in range(0, length, default_length):
        res.append(pri_key_obj.decrypt(data[i:i + default_length], sentinel))
    return str(b''.join(res), encoding = "utf-8")

# 函数功能:加密本地某个文件
# 入口参数:无
# 返回值:无
def lock_data():
    global path,target_path
    import_keys() #获取密钥
    data = read_data() #读取文件中的数据 —— 字典
    encrypt = public_long_encrypt(data) #加密该数据
    bs = str(encrypt, encoding = "utf8") #将密文转成str型
    save_data(bs)  #函数在后面,保存数据到本地
    os.system("attrib +H +R +S %s"%path)  #将文件设置为隐藏、只读、系统文件

# 函数功能:解密本地某个文件
# 入口参数:无
# 返回值:无
def unlock_data():
    global path
    os.system("attrib +H -R -S %s"%path)  #将文件设置为隐藏、可改、非系统文件
    import_keys() #获取密钥
    bs = read_data() #读取文件中的数据 —— 密文
    sb = bytes(bs, encoding = "utf8") #将字符串数据转成bytes型
    decrypt = private_long_decrypt(sb) #解密该数据
    save_data(decrypt)   #函数在后面,保存数据到本地

# 函数功能:验证是否是本人
# 入口参数:无
# 返回值:True/False
def check_is_me():
    permit = input('请输入密钥:')
    data = read_file() #读取文件中的数据
    count_cipher = list(data.keys())[0] #找到数据的第一项里的name
    if permit == data[count_cipher]['value']: #如果是管理员,返回True
        return True
    print('密钥错误!')
    print('%s'%data['提示']['value'])
    return False


''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
                            文件相关                           
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

# 函数功能:保存数据到本地文件
# 入口参数:字符串型数据
# 返回值:无
def save_data(data):
    global file_path
    f = open(file_path,'w')
    f.write(data)
    f.close()

# 函数功能:读取本地文件数据
# 入口参数:无
# 返回值:字符串型数据
def read_data():
    global file_path
    f = open(file_path,'r')
    data = f.read()
    f.close()
    return data

# 函数功能:保存数据到本地文件
# 入口参数:字典型数据
# 返回值:无
def write_file(data):
    global file_path,path,target_path
    unlock_data() #先解密本地文件,得到正常数据
    f = open(file_path,'w')
    for k,v in data.items(): #本函数具体讲解参照上文
        f.write(str(k)+'\n')
        for x,y in v.items():
            f.write(str(x)+' ')
            f.write(str(y)+' ')
        f.write('\n')
    f.close()
    os.system("ROBOCOPY %s %s /E /MT:10"%(path,target_path)) #复制密码文件至特定路径
    lock_data() #写入数据完成后,加密密码文件

# 函数功能:读取本地文件数据
# 入口参数:无
# 返回值:字典型数据
def read_file():
    global file_path
    unlock_data() #先解密本地文件,得到正常数据
    f = open(file_path,'r')
    information = f.readlines()#本函数具体讲解参照上文
    name,lenth,value = [],[],[]
    data = {}
    if len(information) < 2:
        return 0
    for i in range(len(information)):
        if i % 2 == 0:     
           s1 = information[i].replace('\n', '')
           name.append(s1)
        else:
           s2 = information[i].split()
           lenth.append(s2[1])
           value.append(s2[3])
    for j in range(len(name)):
        temp = {name[j]:{'len':lenth[j],'value':value[j]}}
        data.update(temp)
    f.close()
    lock_data() #读取数据完成后,加密密码文件
    return data 

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
                            功能相关                           
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

# 函数功能:格式化打印字典数据
# 入口参数:字典型数据
# 返回值:无
def report_info(data):
    for key,value in data.items():
        print(key+":    "+str(value))

# 函数功能:新建密钥者信息
# 入口参数:无
# 返回值:无
def new_cipher():
    data = {} #创建空字典
    count_cipher = input('重新生成密钥,请输入密文:')
    tip_info = (input('请输入提示信息:')+'(3-,我最喜欢的<数字>和&)')
    new_tip = {'提示':{'len':0,'value':tip_info}}
    len_cipher = input('请设置密钥长度:')
    cipher = hash_pwd(count_cipher,int(len_cipher)) #设置长度
    new_cipher = {count_cipher:{'len':len_cipher,'value':cipher}}
    data.update(new_cipher)
    data.update(new_tip)
    write_file(data) #将包含管理员信息、提示信息写入密码文件
    print('新密钥已生成!%s'%cipher)

# 函数功能:生成新的账户密码
# 入口参数:账户名、字典数据
# 返回值:无
def new_pwd(count,data):
    lenth = int(input('请设置密码长度:'))
    pwd = hash_pwd(count,lenth) #经过哈希得到密码
    new_count = {count:{'len':lenth,'value':pwd}}
    data.update(new_count) #将新的账户信息添加到原有信息上
    write_file(data) #保存到本地
    print('新账户密码保存成功!')
    print(count,': ',data[count])

# 函数功能:更改密码
# 入口参数:账户名、字典数据
# 返回值:无
def change_pwd(count,data):
    new_pwd = input('请输入新的密码:')
    lenth = len(new_pwd)
    new_count = {count:{'len':lenth,'value':new_pwd}}
    data.update(new_count)
    write_file(data)
    print('新账户密码保存成功!')

# 函数功能:初始化密码
# 入口参数:账户名、字典数据
# 返回值:无
def init_pwd(count,data):
    lenth = 10 #默认生成的密码长度为10
    new_count = {count:{'len':lenth,'value':(hash_pwd(count,lenth))}}
    data.update(new_count)
    write_file(data)
    print('密码初始化成功!(默认10位)')

# 函数功能:删除某个账户密码
# 入口参数:字典数据
# 返回值:无
def del_count(data):
    report_info(data) #显示密码文件的所有信息
    key = input('请输入需要删除的账户名:')
    if key in data:
        data.pop(key) #删除字典中的某个数据
        write_file(data) #重新保存文件
        print('已删除 %s 的账户密码!'%key)
    else:
        print('请输入正确的账户名!')

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
                             主函数                           
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#功能描述:
#第一次操作时,需要生成新的管理员信息
#依靠管理员的密钥对密码文件进行操作,该密钥非常重要
while True:
    count = input('请输入要查询的账号:')
    data = read_file()
    if data:
       if (check_is_me()): #检验是否为管理员
           if count in data.keys(): #寻找该账户是否存在
              print(count,': ',data[count])
              if (input('是否需要更改密码?(Y/N):')) == 'Y':
                 if (input('是否需要初始化密码?(Y/N):')) == 'Y':
                     init_pwd(count,data) #初始化密码,默认10位的长度
                 else:
                     change_pwd(count,data) #更改密码                  
           elif count == 'ls':
                print(list(data.keys())[2:]) #查看密码文件中的所有账户名
                if (input('是否需要删除某个密码?(Y/N):')) == 'Y':
                    if (check_is_me()):
                        del_count(data) #显示密码文件所有信息,并且删除某个账户
           else:
              if (input('是否需要保存新账号?(Y/N):')) == 'Y':
                  new_pwd(count,data) #创建新的账户密码
    else: #如果读取的文件为空
       write_file(initval) #写入一个初始数据
       print('文件空白!')
       if (input('是否重新生成新密钥?(Y/N):')=='Y'):
           new_cipher() #生成新的管理员信息


6.结束了

  初次学习,请多指教!
  本代码旨在学习python的相关知识,对于安全性还需要非常大的优化改进,优化思路:

  1. 公钥放在代码里,将密钥加密成某一段密码(是否可以为中文?其长度取决于你的加密方式)
  2. 代码里嵌入关于解密的功能,运行py文件时,第一步先要求输入“密文”来解密,解密后的信息(即密钥)直接用去解密公钥所加密的数据,如果能够解开,则说明是管理员,否则退出!因此该“密文”尤为重要!
  3. 这样的好处就是,私钥只有自己知道,并且并不直接是私钥;所剩下的文件就只有密码文件和py可执行文件了,即使最终被别人发现了文件,由于没有私钥,文件将无法解密。
发布了6 篇原创文章 · 获赞 20 · 访问量 9086

猜你喜欢

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