自制密碼管理器 —— 使用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可執行文件了,即使最終被別人發現了文件,由於沒有私鑰,文件將無法解密。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章