之前我分步完成了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后可以做成选项

简单的测试如下:(因为全篇没图,这个位置我强行贴图了)

cython 加密python3 python写加密程序_加密

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('危险!文件被篡改')

这次可以打印危险提示了

cython 加密python3 python写加密程序_python_02

到这里为止,程序的所有功能模块就完成了,代码都放在了我的github仓库,接下来是漫长的UI学习。