Wolf從零學程式設計-用Python打造簡單加密程式(八)
本篇是最後一篇,加密小程式已經做完了,也可以打包帶走。
這篇列出了幾個改進,我的目的是:
- 儘量改善使用者體驗
- DEBUG
一、Fix the Bugs
函式引數順序不同導致BUG
之前寫完太激動,昨天耐著性子把所有功能走了一遍(原諒我,還不會寫測試),結果是這樣的:
- 生成金鑰正常
- DES加解密正常
- RSA加解密正常
- 混合模式讀不出金鑰,報錯資訊如下:
File "E:\crypto\crypto\base.py", line 44, in getPubKey
pub_key = rsa.PublicKey.load_pkcs1(keydata)
File "C:\Python 34\lib\site-packages\rsa\key.py", line 75, in load_pkcs1
return method(keyfile)
File "C:\Python34\lib\site-packages\rsa\key.py", line 243, in _load_pkcs1_pem
der = rsa.pem.load_pem(keyfile, 'RSA PUBLIC KEY')
File "C:\Python34\lib\site-packages\rsa\pem.py", line 91, in load_pem
raise ValueError('No PEM start marker "%s" found' % pem_start)
ValueError: No PEM start marker "b'-----BEGIN RSA PUBLIC KEY-----'" found
- 數字簽名簽名、正反驗證均正常
為此我同時把混合模式、RSA,以及被呼叫的base都開啟,一行一行的比對,結論是混合模式和RSA對應的呼叫部分完全相同,不可能出錯。我又想到具體的方法呼叫發生在crypto.py裡,一併開啟重新看程式碼,發現以下不同:
# crypto.py
mymix.encMix(rawfilename,key_filename,mode,operation)
# mymix.py
def encMix(key_filename,rawfilename,mode,operation) :
# myrsa.py
def encFile(rawfilename,key_filename,mode,operation):
唯一的區別就是:encMix()的引數名位置和其他兩個不同,當crypto中呼叫時,會把原始檔的路徑傳給金鑰檔案,造成無法讀取。我自己編寫時,都是每個函式單獨測試,檔案路徑是input的,單個模組內使用引數順序都是一樣的,所以沒發現問題。
果然,修改引數順序後,執行正常。到這裡整個程式就可以完全運行了。
操作成功後焦點停在DES初始值
測試時常用DES加密,發現操作成功後雖然輸入框都清空了(本篇後面的一處改進),但滑鼠焦點卻在DES初始值處,這時除非使用者輸入8位元組,否則必然會出現一次彈窗。
查詢發現,在DES模式中,DES初始值是最後一個使用焦點輸入的內容,步驟最後的原始檔通常都是通過瀏覽檔案選擇的。
之前有想過讓每個輸入框預設為’disabled’,待選擇模式後再判斷啟用,這樣也好看,剛巧也可以解決這個BUG。
# ginterface.py
# 元件預設禁用狀態
des_key_entry = Entry(key_frame,**state='disabled'**,textvariable=des_key)
#執行按鈕呼叫方法
def cryption():
...
#清空輸入框
if textVar == '操作完成':
mode_choice.set('')
operation_choice.set('')
des_key_entry.delete(0,END)
des_key_entry['state'] = 'disabled' #清空後恢復禁用狀態
二、Improvement
DES金鑰有效性驗證
DES金鑰和初始值要求必須是8個位元組,entry元件的驗證功能就可以完成,沒必要使用事件繫結。
對於entry元件的內容驗證,可以看看魚C的講解,很詳細
我希望當用戶把焦點離開輸入框時觸發驗證,如果不足8位元組則清空輸入框,並彈出提示框;為了清空輸入框,就必須呼叫delete()方法,所以沒辦法讓DES金鑰和初始值驗證使用同一個方法;彈出提示框可以用tkinter的標準對話方塊messagebox
# ginterface.py
# 驗證DES金鑰和初始值合法性
def valiDeskey():
if len(des_key.get()) == 8:
return True
else:
des_key_entry.delete(0,END) # 不是8位元組就直接清空
messagebox.showerror('輸入錯誤','請輸入8位元組DES金鑰',default='ok',icon='warning')
return False # 驗證函式的返回必須是True或False
# DES金鑰
...
des_key_entry = Entry(key_frame,textvariable=des_key,validate='focusout',validatecommand=valiDeskey,width=10,show='*')
...
DES初始值的驗證完全一樣,只是把相應的函式名和變數名更改。
目前這種寫法要求使用者一旦選擇輸入DES金鑰,就要確保輸入8個位元組,中途做任何其他的事情都會使輸入框失去焦點,即彈出提示。
改進模式選擇
目前的邏輯是:使用者選擇模式和操作,當焦點離開時,會根據已選模式和操作,判斷金鑰區哪一個輸入框可用,但是我設定了預設值是DES和加密,使用者有可能壓根就不會把焦點放在模式選擇框,因此不會對輸入框狀態進行更改,這種情況我是不允許的!
把模式和操作設定預設值的程式碼刪掉,妥妥的逼使用者選擇。
操作成功後清空輸入
使用者很可能在操作一次DES後緊接著開始一次RSA操作,這時應該讓軟體介面恢復到剛開啟的狀態,也就是把所有的輸入框都清空。我把清空操作新增在了’執行操作’的方法裡,且只有當用戶操作成功後才會清空。
# ginterface.py
def cryption():
...
#清空輸入框
if textVar == '操作完成':
mode_choice.set('')
operation_choice.set('')
des_key_entry.delete(0,END) # Entry元件沒有set方法
des_IV_entry.delete(0,END)
key_filename_entry.delete(0,END)
sig_filename_entry.delete(0,END)
rawfilename_entry.delete(0,END)
新增對話方塊清空功能
當用戶覺得對話方塊內容過多時,可以一鍵清空。同時為了避免使用者在對話方塊隨意輸入內容,我把對話方塊的預設state設為disabled,只有當列印操作進度或清空時短暫恢復normal
# ginterface.py
text = Text(height=10,width=60,bd=3,relief=SUNKEN,wrap=WORD,state='disabled')
#高度、寬度、邊框寬度和樣式、按單詞換行
text.pack(padx=1,pady=5)
def empty():
text['state'] = 'normal'
text.delete(1.0,END) #1.0是起始位置
text['state'] = 'disabled'
empty_button = Button(text='清空對話方塊',command=empty)
empty_button.pack(padx=20,pady=5,side=RIGHT)
捕獲錯誤資訊
為了讓使用者隨心輸入隨時可以點選’執行操作’按鈕,需要把程式碼執行中的錯誤資訊捕獲,並且對於使用者選擇的錯誤模式給出提示。
將原先的
# ginterface.py
def cryption():
...
textVar = crypto.doCrypto(mode,operation,des_key,des_IV,key_filename,sig_filename,rawfilename)
text['state'] = 'normal'
text.insert(END,textVar)
text.insert(END,'\n')
text['state'] = 'disabled'
改為
def cryption():
...
try:
textVar = crypto.doCrypto(mode,operation,des_key,des_IV,key_filename,sig_filename,rawfilename)
except:
textVar = '請正確輸入各項引數'
text['state'] = 'normal'
text.insert(END,textVar)
text.insert(END,'\n')
text['state'] = 'disabled'
然後在’crypto.py’中,根據使用者的選擇給返回值賦值,例如使用者選擇了DES-簽名,就返回’請選擇正確的操作’,測試結果如圖:
給進度對話方塊安裝垂直滾動條
tkinter的Scrollbar元件可以給Text元件安裝垂直和水平滾動條,我需要做的事情有:
- 新建一個Frame元件,作為對話方塊和滾動條的父元件
- 設定Text元件的yscrollcommand選項為Scrollbar元件的set()
- 設定Scrollbar元件的command選項為Text元件的yview()
#ginterface.py
#對話方塊框架,此框架作用是方便排版
text_frame = Frame(root)
text_frame.pack()
#滾動條和對話方塊
textbar = Scrollbar(text_frame,takefocus=False) #滾動條不需要焦點
textbar.pack(side=RIGHT,fill=Y)
text = Text(text_frame,height=10,width=60,bd=3,yscrollcommand=textbar.set,relief=SUNKEN,wrap=WORD,state='disabled')
#高度、寬度、邊框寬度和樣式、按單詞換行
text.pack(side=RIGHT,fill=BOTH)
textbar['command'] = text.yview #yview是Text元件自帶方法
清空無效輸入框
使用者可能會先選擇DES模式,輸入金鑰後發現選錯了,應該是RSA模式,這時候肯定是直接更改模式,已經輸入的DES金鑰需要在禁用前自動清空。
只需要在模式判斷里加一行清空就可以,不過只有需要禁用的輸入框新增這一行。
# ginterface.py
def judgMode(event):
if mode.get() == 'DES':
des_key_entry['state'] = 'normal'
des_IV_entry['state'] = 'normal'
**key_filename_entry.delete(0,END)**
**sig_filename_entry.delete(0,END)**
key_filename_entry['state'] = 'disabled'
sig_filename_entry['state'] = 'disabled'
目前為止這個小軟體就算完工了,還有一些考慮新增的功能暫時想不到辦法,比如:
- 只允許通過瀏覽檔案選擇檔案,避免使用者輸入的錯誤
- 進度對話方塊實時列印程式碼執行進度和操作耗時
醜陋的程式碼也需要不斷優化,總之我會邊學習邊進行,github倉庫會即時更新。