之前我分步完成了RSA加密DES密钥、RSA加密大文件、DES和RSA混合加密,在这一篇要把这些功能以及数字签名和验证整合起来。
血淋淋的事实告诉我们,如果要写博客,就一定要在敲代码的同时写!不然就会像我现在这样,看着改完的代码只能回忆起当初遇到的问题,却实在不愿意重现当时的场景。列出的问题也都没有实例和截图支撑。
在整合过程中遇到了以下问题:
1.文件路径
我的目标是让用户选择文件,程序自己识别出路径。可我目前没有UI,自己调的时候只能手动输入,我希望可以输入原始的路径,程序为其去转义。
这时发现问题:把路径写在代码里,就必须以去掉转义的形式写入,否则根本不可能执行下一行代码;但是用户是不可能去掉转义的,他们选择的必然是普通的windows文件路径。(如何去转义)例如:
>>> filepath = 'C:\Users\Administrator\Desktop\test.txt'
这行代码是无法执行的,只有在这时就去转义才可以。
之后我尝试使用input():
>>>filepath = input('请输入路径:')
请输入路径:C:\Users\Administrator\Desktop\test.txt
>>>filepath
C:\\Users\\Administrator\\Desktop\\test.txt
Python已经善解人意的帮我转换好了,文件路径的问题就暂时这么解决。
2.密钥文件
同样我希望用户选择密钥文件,程序自动读取其中的密钥,那么我首先需要生成密钥文件。起初我的想法很简单,把生成的公钥和私钥分别写进密钥文件中,加解密时读取就好了,我的代码是这样的:
#生成密钥文件
def geneKeys(bits_num):
(pub_key,priv_key) = rsa.newkeys(bits_num)
print(pub_key) # 用于验证密钥格式
with open('pub_key.txt','w') as f1, open('priv_key.txt','w') as f2:
f1.write(str(pub_key))
f2.write(str(priv_key))
f1.close()
f2.close()
#读取公钥
def getPubKey(keyfile):
keyfile = 'pub_key.txt'
with open(keyfile,'r') as f:
pub_key = f.read()
print(pub_key) # 用于验证得到的密钥格式添加了print
f.close()
执行后是这样的:
PublicKey(110033558209536852961104748072325524170015000991015977032379850324526371006049699376882883630502167366123178380691560970530014782054865549746026675957941702625202522315342068759285591171619104078716050913217288395405360879790773854123724004013945588678161196488714307142003225792589258676722681038564826071359, 65537)
PublicKey(110033558209536852961104748072325524170015000991015977032379850324526371006049699376882883630502167366123178380691560970530014782054865549746026675957941702625202522315342068759285591171619104078716050913217288395405360879790773854123724004013945588678161196488714307142003225792589258676722681038564826071359, 65537)
从外表看起来一模一样,但是调试加密大文件时,我读出的公钥并不能使用,代码报出了:
TypeError: Public key required, but got ‘PublicKey(110033558209536852961104748072325524170015000991015977032379850324526371006049699376882883630502167366123178380691560970530014782054865549746026675957941702625202522315342068759285591171619104078716050913217288395405360879790773854123724004013945588678161196488714307142003225792589258676722681038564826071359, 65537)’
此时我的内心有一万头跑过~~~又回头去翻python-rsa的文档,原来作者提供了相应的储存和读取方法,使用save_pkcs1()和load_pkcs1()圆满解决,见base.py代码。
3.部分方法重复使用
- 密钥文件生成,一次生成文件可在RSA加密、混合加密、签名中多次使用
- 密钥读取,每次RSA加解密操作都会使用
- 新文件命名,任何加解密操作都会使用
于是把这几个功能拿出来,简单修改后丢进了一个base.py模块
4.汉字解码问题
代码中明明已经声明了’utf-8’编码,但就死活不让输入中文,报"UnicodeDecodeError:‘utf-8’ codec can’t decode ",这个问题遇到过两次:
- 第一次,第一天关机,第二天再运行就不报错了
- 第二次,当场新建个文档,把模块代码完全复制过去运行,不报错
5.DES密钥密文长度
如果我规定RSA只能选择1024位,就没有这个问题,但很可惜,我不仅没这么规定,甚至只给出1024、2048、3072、4096这么几个没什么约束的建议,我总不能阻止用户选择1020位,那么密文就是1020位的,于是问题产生了:
在混合加密中,如何正确的从密文文件中读出DES密钥密文?
经试验,1020位RSA加密后的密文是128字节!没错,和1024位加密后长度一致;也就是说,从1017到1024位的RSA产生的密文都是128字节,1016位产生的密文是127字节。于是我写了一个方法计算密钥密文的长度。
#计算DES密钥密文长度
def size_in_bytes(priv_key):
# 计算n模8的余数
remainder = (int(len(bin(priv_key.n))) - 2) % 8
bytes = (int(len(bin(priv_key.n))) - 2) / 8
# 模不为0则要在地板除的基础上加1
if remainder:
bytes = (int(len(bin(priv_key.n))) - 2) // 8 + 1
return bytes
n转换成二进制后以’0b’开头,因此位数要减掉2。
6.DES初始值
DES的密钥只有8个字节,但是一旦选择CBC模式,就还需要选择一个初始值,并且解密时也必须输入相同的初始值,这等同于让用户记住16个字节的密钥。我考虑能不能让初始值随机生成,并随密文一起传送,解密时自动使用,这样用户也省事。
问题是初始值是解密时使用的,我只能发送一个文件,又不能把初始值明文写在密文文件里,在DES加密中暂时只能让用户输入了。
不过在混合加密里是完全可以实现的,密钥和随机生成的初始值一起被加密,接收方解密密钥后直接用来解密文件。因此在混合加密中,初始值选择随机生成8字节的字母和数字组合。
import string,random
des_IV = ''.join(random.sample(string.ascii_letters+string.digits,8))
看了一遍代码,只记得这几个问题了。
7.签名和验证
目前逻辑功能还差一个签名和验证,python-rsa提供了‘MD5’、‘SHA-1’、‘SHA-256’、‘SHA-384’ 、‘SHA-512’等hash算法,以及简单的方法sign()和verify()。
先贴代码:
import rsa
import base
#签名
def sigFile(keyfile,rawfile,hash_method):
message = open(rawfile,'rb').read()
priv_key = base.getPrivKey(keyfile)
signature = rsa.sign(message,priv_key,hash_mode)
with open('signature.SIG','wb') as f:
f.write(signature)
f.close()
print('签名文件已生成')
#验证
def veriFile(keyfile,rawfile,sigfile):
message = open(rawfile,'rb').read()
pub_key = base.getPubKey(keyfile)
signature = open(sigfile,'rb').read()
if rsa.verify(message,signature,pub_key) == True:
print('检测完毕!文件未被篡改')
else:
print('危险!文件被篡改')
- 代码中涉及读写文件的,我使用了全部’rb’的笨办法
- hash算法目前规定了使用SHA-1,有了UI后可以做成选项
简单的测试如下:(因为全篇没图,这个位置我强行贴图了)
Too young!作者的代码里没有’return False’,在验证中有多个步骤,任何一个步骤失败都会直接报’Verification failed’。
于是我把验证改成简单的异常捕获:
#验证
def veriFile(keyfile,rawfile,sigfile):
message = open(rawfile,'rb').read()
pub_key = base.getPubKey(keyfile)
signature = open(sigfile,'rb').read()
try:
print(rsa.verify(message,signature,pub_key),'检测完毕!文件未被篡改')
except:
print('危险!文件被篡改')
这次可以打印危险提示了
到这里为止,程序的所有功能模块就完成了,代码都放在了我的github仓库,接下来是漫长的UI学习。