本篇是最后一篇,加密小程序已经做完了,也可以打包带走。
这篇列出了几个改进,我的目的是:
- 尽量改善用户体验
- 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:\Python34\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'
目前为止这个小软件就算完工了,还有一些考虑添加的功能暂时想不到办法,比如:
- 只允许通过浏览文件选择文件,避免用户输入的错误
- 进度对话框实时打印代码执行进度和操作耗时