如何使用RSA 对数据加解密和签名验签?一篇文章带你搞定

2022-04-08 13:15:44 浏览数 (1)

前言

加密是指利用某个值(密钥)对明文的数据通过一定的算法变换加密(密文)数据的过程,它的逆向过程叫解密。

业务场景:一般情况下,互联网上流动的数据不会被加密,无法避免这些数据泄露窃取,实际数据上传过程中,为了保证数据不被泄露、实现安全数据传输,出现了各种加密技术,本次主要分享如何通过python来实现非对称加密算法RSA加解密。

RSA算法简介

加密和解密使用相同的密钥叫对称加密方式,而非对称加密算法(公钥加密方式)RSA加密与解密分别用不同的密钥,公钥用于加密,私钥用于解密。

比如发送者S要给接受者R传输报文信息,为了避免信息泄露,秘钥签发者R事先通过RSA加密算法生成秘钥对,并且将公钥事先给到S,私钥则自己保留,S向R传输信息时,先用R提供的公钥加密报文,然后再将报文传输给R,R获取加密后的信息,再通过其单独持有的私钥解密报文,即使报文被窃听,窃听者没有私钥,也无法解密。公钥对外公开的,私钥自己保留,由于公钥是公开的,任何人都能拿到(会同时给到多个人),都可以使用公钥来加密发送伪造内容,因此,验证发送者的身份,确保报文的安全性显得非常重要。

考虑到一种情况:发送者S获取接收者R的公钥时,被中间人A获取到了这个公钥,通过公钥对信息加密,冒充S来给R发篡改的报文,同样R接受信息后也能够通过持有的私钥解密报文数据,接受者无法辨别数据发送者身份,存在报文非法修改风险,为了区分发送者的身份,那么这个时候我们就要用到签名。

签名原理:对报文做摘要,能防止被篡改。发送方对报文原文做加盐hash摘要,把加密原文和摘要一起发送给接收方,接收方解密后,用同样的hash方法计算并比对摘要,就能判断原文是否被篡改。

签名过程:发送者S同样也生成了一对秘钥,事先将公钥给到R,在发送消息之前,先用R给的公钥对报文加密,然后签名使用S自己私钥来签名,最后将加密的消息和签名一起发过去给R,接受者R在接收到发送者S发送的数据后,首先使用S的公钥对签名信息进行验签,确认身份信息,如果确认是发送者S,然后再R才利用私钥对加密消息进行解密,从而隔离非法数据包的接收。

这样一来,发送过程信息被获取,没有R的私钥无法解密信息,即使获取到发送者S的公钥,想要仿造发送信息没有S的私钥无法签名,同理R给S回复信息时,可以通过R的公钥加密,自己的私钥生成签名,S接收到数据使用同样的方式进行解密验证身份。私钥加签,公钥验签,这样就能确保只有私钥持有者也就是发送者能合法发送数据。

加签:

验签:

Python实现RSA加解密相关知识要点

1、首先安装加密库:pip install pycryptodome

python中要使用到crypto相关的库,使用的第三方库是 pycryptodome,其为pycrypto的延伸版本。

  • rsa文档地址:https://stuvel.eu/files/python-rsa-doc/index.html
  • pycryptodome文档地址:https://www.pycryptodome.org/en/latest/src/cipher/classic.html

2、字符串和Bytes互相转换使用encode()和decode()方法

加密与解密操作的时候,要确保我们操作的数据类型是Bytes。

3、生成秘钥对

秘钥对一般由密钥签发者来生成提供。通过RSA校验生成自己的公钥,私钥,这里生成固定的备用。

代码语言:javascript复制
from Crypto import Random
from Crypto.PublicKey import RSA

# 伪随机数生成器
random_gen = Random.new().read

# 生成秘钥对实例对象:2048是秘钥的长度
rsa = RSA.generate(2048, random_gen)

# 获取私钥,保存到文件
private_pem = rsa.exportKey()
with open('private.pem', 'wb') as f:
    f.write(private_pem)

# 获取公钥保存到文件
public_pem = rsa.publickey().exportKey()
with open('public.pem', 'wb') as f:
    f.write(public_pem)

生成秘钥对的时候,可以指定生成秘钥的长度,一般推荐使用 1024bit, 1024bit 的 rsa 公钥,加密数据时,最多只能加密 117byte 的数据,数据量超过这个数,则需要对数据进行分段加密;为保证更安全,尽量使用 2048bit ,最多只能加密245byte 长度的数据。

秘钥对生成如下格式:

代码语言:javascript复制
公钥
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtas/LB41SKhFtu49b7TZ
oiTQ ABoT6b8REs8PuuE5PZHByA/IXvEGrNl5osOV79OMmgAxiZGirXOfWZMoZuB
7Htu97Fyyh9fc9WLlkaCPbkN6LDp6EEiW seQbcmJHRSgQUyyzu/T9/x9aA2aoHv
JeRO59kDmYfyoyg5 rFfgzy YizZWqFRTLV9ZXKecIfAy7Opt3rnc2EaiFDr7/zw
KJmIxOfESwmhmRV1RyMj3O0yFD1xnkZ7ouh 4OExwNjTdUVpoQDz2HaU9QC/tEIX
TyJX36sJSyciLb 8itcfhegnBiNxRYZtwzsq8o9ASBcuXjzZPU7zlVGQTxIK7lWX
PwIDAQAB
-----END PUBLIC KEY-----
私钥
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAtas/LB41SKhFtu49b7TZoiTQ ABoT6b8REs8PuuE5PZHByA/
IXvEGrNl5osOV79OMmgAxiZGirXOfWZMoZuB7Htu97Fyyh9fc9WLlkaCPbkN6LDp
6EEiW seQbcmJHRSgQUyyzu/T9/x9aA2aoHvJeRO59kDmYfyoyg5 rFfgzy YizZ
WqFRTLV9ZXKecIfAy7Opt3rnc2EaiFDr7/zwKJmIxOfESwmhmRV1RyMj3O0yFD1x
nkZ7ouh 4OExwNjTdUVpoQDz2HaU9QC/tEIXTyJX36sJSyciLb 8itcfhegnBiNx
RYZtwzsq8o9ASBcuXjzZPU7zlVGQTxIK7lWXPwIDAQABAoIBAAJSmhN20wItGi/g
dBgH05mympqPhF9/iFpZLRm2CiduDKXT dEN4FZ7dH74Yfd2O/1oZGYkJ1YiEYdI
T3K1GcalV1uPjbx1OG1Mzb5fA86rnVI1yaMAcWK3RIl2wLD9Ob02wBnM5EFhrbOQ
8YRc4x4nzc7bo/BxEzjJzJMrdGF2cYeVMlASxA7IE09W FFvPAWLR1nlfx6GoZuJ
edvQtrDxgRPLRluGWRdHmASoYHgONBJmV9NjLeOyGPwK32obdeE2vQLfaghDRvmI
GJ7LAc0yYRS7Aa82/BF8/x/r/BR3o7 bFg1t3n SFXx8eCUSsBCKL3BNu1HiyWWj
2LGFlp0CgYEAyvC/CThHuAocIp4p7nyB6Wwx0fxxgLvcUtIfP77yOZDm3T2UK6BX
gLEMb7Fpmo6uH39ewubQtSxMrJH20DUA7MyRVJQUiM bSvJ4jG/T I1bi7BfVuL9
QDbW zQvQ9CHZ0ZaijelolSBzMfO6/l4km6zgeM/Wf3ypkvvX k9VfMCgYEA5SrA
u rnUpPb1vCBZ DZrPkA0hYB N eZ0wmrM4m0dP6xI084UpU0PEdL o8FE9azlN7
l7tWtdmx003jy3pnzr6DMMIfgB92bHCfMMPq7ddSbfz6ul/kr67oUtMAdcy odrY
Ah59 q9oyIeVZLtBKvlzaTp6httwm/kKL2n0UIUCgYEAhFz7rM7JcE8fxLB2Vvdc
YFvSLszBVx6weFBWU2R Zm NNHXqg33kNKrFmsATSdyP0zlnHCYhsFlBdTkKywgX
H1vZ2llu/0CxX/PADpENp0rDj9usg2Yvmcdq9pM11LxY5FIt0YK0BKmrs14LJzwi
mRec zW150NMFYznhx4AhGMCgYAnjF9CjuFo4Nd5mnvan3UxYq9/kgi5GG5PyVaL
T/BnGbwXG4C8KIXGoTW2RSglISS8oq bmdr2 yCzJKgBP5iWl04wpe lvshDIpR2
Z/ktHpG9JYFnlJD0uKyjToKv0au8ZvYMN5LqJkdhA/UGM0Kl1fLS4CKxD0G5yRq2
4AQnuQKBgQDEyPS12CtCkOZMCwoONWGlnhsTmwxZJpVbCGmFucpuJagQiIIpHnJq
3LlU9Z3wh09kGFHJlSSzdMkHzZ4/x6ra4zuGObClHju6v3I5sY3/iPw97zDTiq0P
x7f9hR//cY9wnhjbaQkpvNooHXTHL3PZC8AN4Ud aKzzbwFWrUBYxw==
-----END RSA PRIVATE KEY-----

计算公式如下:秘钥长度/8-11 = 最大加密量(单位:byte)

4、Base64编码

base64 是网络上最常见的用于传输8bit字节代码的编码方式之一,是一种基于64个可见字符来表示二进制数据的方法。用来将非ASCII字符数据转换成ASCII字符的一种方法,特别适合在HTTP协议下快速传输数据。比如邮件,ASCII 控制字符 、中文、图片二进制数据等。

基本原理

base64 将 ASCII 码 或者二进制编码成只包含 A~Z、a~z、0~9、 、/ 这64个字符(26个大写字符、26个小写字符、10个数字、 /)。通过3个8bit字节( 3 x 8 = 24 )编码成4个6位字节(4 x 6 = 24),在每个6位字节前补两个0,形成4个8字节形式。

编码规则

base64要求把每3个8bit字节转换为4个6bit的字节,然后把6bit的字节高两位添加为0,组成4个8bit的字节,理论上将比原来长1/3。如果要编码的二进制数据不是3的倍数,数据长度除以3的余数就是2或者1,转换的时候结果不足6位的,用0来填充,之后在6位前面补两个0,转换完空出的结果用 = 来补位,最后保证编码出来的字节为4的倍数。解码的时候,等号会自动被去掉。

注:由于标准的Base64编码后可能出现字符 和斜扛/, 和/在URL中不能直接作为参数,因此,Base64提供了urlsafe_b64encode方法将 和/分别转换为横杠-和下画线_,使用urlsafe_b64decode方法将横杠-和下画线_还原为字符 和斜扛/。

Python实现RSA加解密和签名验签类

本文将RSA加密方法写成一个类,支持包含中文的长字符串分段加解密。

注:经过分段加密的数据,在进行解密的时候我们也要分成多段解密,然后解密之后再进行拼接成原串,加密签名与解密验签注意保持原串一致。

代码语言:javascript复制
from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_PKCS1_v1_5
import base64
from Crypto.Signature import PKCS1_v1_5 as Sig_pk
from Crypto.Hash import SHA

class RSACipher():
    '''
    RSA加密、解密、签名、验签工具类
    备注:# RSA的加密机制有两种方案一个是RSAES-OAEP,另一个RSAES-PKCS1-v1_5
        # 对同样的数据,用同样的key进行RSA加密, 每次的输出都会不一样;但是这些加密的结果都能正确的解密
    '''

    def read_xml(self,xmlfile):
        '''
        读取待加密明文方法
        '''
        with open(xmlfile, 'r', encoding="utf-8") as file:
            # 用open()将XML文件中的内容读取为字符串
            xmlstr = file.read()
            print(xmlstr)
        return xmlstr

    def encrypt_file(self,encrypt_file):
        '''
        保存加密后密文方法
        '''
        with open(encrypt_file, 'rb') as f:
            message = f.read()
        return message

    def Encrypt(self, message, publicKeyfile, out_file):
        '''
        加密方法
        :param message: 需要加密的明文
        :param publicKeyfile: 公钥文件
        :param out_file: 输出密文
        :return: 加密后的文本
        '''
        with open(publicKeyfile, 'r') as f:
            publicKey = f.read()
        pubKey = RSA.importKey(publicKey)
        cipher = Cipher_PKCS1_v1_5.new(pubKey)
        message = message.encode()

        # 分段加密,加密长度byte为8的倍数,最长不超出最大加密量(单位:byte)=秘钥长度/8-11
        length = len(message)
        default_length = 245
        offset = 0
        res = bytes()
        while length - offset > 0:
            if length - offset > default_length:
                _res = cipher.encrypt(message[offset:offset   default_length])
            else:
                _res = cipher.encrypt(message[offset:])
            offset  = default_length
            res  = _res
        encrypt_text=base64.b64encode(res)

        with open(out_file, 'wb') as f_w:
            f_w.write(base64.b64encode(res))
        return encrypt_text


    def Decrypt(self,message, privateKeyfile, out_file):
        '''
        解密方法
        :param message: 加密后的密文
        :param privateKey: 私钥文件
        :param out_file: 输出明文
        :return: 解密后的文本
        '''
        with open(privateKeyfile, 'r') as f:
            privateKey = f.read()
        rsaKey = RSA.importKey(privateKey)
        cipher = Cipher_PKCS1_v1_5.new(rsaKey)
        randomGenerator = Random.new().read
        message = base64.b64decode(message.decode())
        res = []
        for i in range(0, len(message), 256):
            res.append(cipher.decrypt((message[i:i   256]),randomGenerator))
        plainText = bytes(b"".join(res)).decode()
        print(plainText)

        with open(out_file, 'w', encoding='utf-8') as f_w:
            f_w.write(plainText)
        return plainText

    def sign(self,message, private_sign_file):
        '''
        签名方法
        :param message: 需要签名的文本
        :param private_sign_file: 私钥文件
        :return: 签名信息
        '''
        with open(private_sign_file, 'r') as f:
            private_sign = f.read()

        message = message.encode()

        private_key = RSA.importKey(private_sign)
        # 根据sha算法处理签名内容
        hash_value = SHA.new(message)
        # 私钥进行签名
        signer = Sig_pk.new(private_key)
        signature = signer.sign(hash_value)

        result=base64.b64encode(signature).decode()

        return result # 将签名后的内容,转换为base64编码

    def verify(self,message,public_sign_file,signature):
        '''
        验签方法
        :param message: 需要验签的文本
        :param public_sign_file: 公钥文件
        :param signature: 签名信息
        :return: 验签结果
        '''
        with open(public_sign_file, 'r') as f:
            public_sign = f.read()
        signature = base64.b64decode(signature)
        # 将签名之前的内容进行hash处理
        public_key = RSA.importKey(public_sign)
        print(public_key)
        # 验证签名
        hash_value = SHA.new(message.encode())
        verifier = Sig_pk.new(public_key)
        return verifier.verify(hash_value, signature)


if __name__ == '__main__':
    #创建RSA加密实例
    rsacipher = RSACipher()
    xmlfile = r'new1.xml'
    message=rsacipher.read_xml(xmlfile) #待加密明文
    encryptFile = "encrypt.txt"  #加密后密文
    publicKeyfile="rsa.pub" #公钥加密

    # 加密
    encrypt_text = rsacipher.Encrypt(message,publicKeyfile,encryptFile)
    print('加密后:n%s' % encrypt_text)

    # 签名
    private_sign_file="private.pem"  # 私钥签名
    signature = rsacipher.sign(message,private_sign_file)
    print('签名:n%s' % signature)

    # 解密
    decryptFile ="deencrypt.txt" #输出解密内容
    privateFile = "rsa.key"  #私钥解密
    decrypt_text = rsacipher.Decrypt(encrypt_text,privateFile,decryptFile)
    print('解密后:n%s' % decrypt_text)

    # 验签
    pubic_sign_file = "public.pem"  # 公钥验签
    result = rsacipher.verify(decrypt_text,pubic_sign_file,signature)
    print('验签:n%s' % result)

文中包含所有源码,自己动手创建两套公钥私钥、测试文本即可,快动手试一下吧。

0 人点赞