从pyCryptodome库看RSA如何加解密长消息

前言:网上有很多关于使用pyCryptodome库进行RSA加解密以及签名,但是大部分都是根据该库的开发文档进行的,只能算是最基础的版本。我们知道,RSA是明文分组加密,也就是说,它的加密的消息是定长的。但是现实场景中,我们大部分遇上的都是不定长消息,而超长消息在现实中更为常见。所以如何切割与填充,这是在实际应用中值得关注的问题所在。

观前提醒

本博客代码是查阅和分析pyCryptodome库关于RSA源码后,写的实际调用的代码。采用的是python语言编写,如果是Java,实际上也可以参考思路进行改造。

该博客写作之前,已经将分析源码的PPT上传,客官可移步: 基于RSA的PyCryptodome库讲解.pptx.(为卑微的一级菜鸡捐献一点点积分)

加解密之 ---- pkcs

网上的大部分代码都是遵循pkcs协议的,但是其测试的message长度都是很小。而实际中,pkcs协议只能抵御选择明文攻击(简单来讲,同样一个消息经过同样一个加密流程,最后出的密文结果还会是不一样的,这样可以防止别人使用各种搭配明文,推出密文甚至是秘钥。)pkcs协议是比较早期的协议,可以满足一般的加解密需求。

from Crypto.PublicKey import RSA
import Crypto.Cipher.PKCS1_v1_5 as cipher

def generate_key(bits):
	'''
    :param bits: the length of RSA key
    :return: RSAKey object
    '''
    return RSA.generate(bits)

def encrypt(message, pk):
	'''
    :param message: the message would be encrypted
    :param pk: the public key
    :return: a cipher text for the message though encrypt
    :rtype: bytes
    '''
    cipher_obj = cipher.new(RSA.importKey(pk))
    org_bytes = message.encode()
    length_en = RSA.RsaKey.size_in_bytes(RSA.importKey(pk)) - 11 # 2048/8-11=245
    
    res_en = b''
    for i in range(0, len(org_bytes), length_en):
        res_en += cipher_obj.encrypt(org_bytes[i: i + length_en])
    cipher_text = res_en
    return cipher_text

def decrypt(cipher_text, sk):
	'''
    :param message: the cipher text would be decrypted
    :param pk: the private key
    :return: a message for the cipher text though decrypt
    :rtype: string
    '''
    cipher_obj = cipher.new(RSA.importKey(sk))
    length_de = RSA.RsaKey.size_in_bytes(RSA.importKey(sk))

    res_de = b''
    for i in range(0, len(cipher_text), length_de):
        res_de += cipher_obj.decrypt(cipher_text[i:i + length_de], 'DecryptError') # for pkcs
    plaint_text = res_de.decode()
    return plaint_text
    
if __name__ == "__main__":
    key = generate_key(2048)	# 2048 is the length of RSA key
    pk = key.publickey().export_key()	# for public key
    sk = key.export_key()	# for private key

	message = "We are different, work hard!" * 100
    cipher_text = encrypt(message, pk)
    plaint_text = decrypt(cipher_text, sk)
    print(message == plaint_text)

在这里插入图片描述
注意这里length_enlength_de是不一样的。最主要的原因是message最少要留出11个比特去进行填充,解密的时候就只需要分组密码字符长度就可。也就是说,当你的message很短,ps可以动态填充到分组密码长度,但是ps最少要8个比特。然后pkcs的解密过程如果出现错误,错误返回的信息可以由你自己定(比如上方的'DecryptError'),这样就不用try-except啦。

加解密之 ---- oaep

在上方之前讲到,pkcs只能抵御选择明文攻击,但是不能抵御选择密文攻击(敌手可以通过对密文加点料再进过解密机的解密得到明文消息)。这时候就需要换种协议,像接下来要讲的oaep就能很好的解决这个问题。
在这里插入图片描述
oaep的实现过程比较复杂,该文只讲述实际调用,关注点在实际环境中,故而不对内部原理进行解析。

from Crypto.PublicKey import RSA
import Crypto.Cipher.PKCS1_OAEP as cipher
from Crypto import Hash

def generate_key(bits):
	'''
    :param bits: the length of RSA key
    :return: RSAKey object
    '''
    return RSA.generate(bits)

def encrypt(message, pk, hasher=Hash.SHA1):
	'''
    :param message: the message would be encrypted
    :param pk: the public key
    :param hasher: sha1 is the default in the source code
    :return: a cipher text for the message though encrypt
    :rtype: bytes
    '''
    cipher_obj = cipher.new(RSA.importKey(pk))
    org_bytes = message.encode()
    length_en = RSA.RsaKey.size_in_bytes(RSA.importKey(pk)) - 2 * hasher.digest_size - 2  # 2048/8-(2*20+2) -- SHA1
    
    res_en = b''
    for i in range(0, len(org_bytes), length_en):
        res_en += cipher_obj.encrypt(org_bytes[i: i + length_en])
    cipher_text = res_en
    return cipher_text

def decrypt(cipher_text, sk):
	'''
    :param message: the cipher text would be decrypted
    :param pk: the private key
    :return: a message for the cipher text though decrypt
    :rtype: string
    '''
    cipher_obj = cipher.new(RSA.importKey(sk))
    length_de = RSA.RsaKey.size_in_bytes(RSA.importKey(sk))

    res_de = b''
    for i in range(0, len(cipher_text), length_de):
    	try:
        	temp = cipher_obj.decrypt(cipher_text[i:i + length_de])  # for oaep
        exceptreturn "解密失败"
        res_de += temp
    plaint_text = res_de.decode()
    return plaint_text
    
if __name__ == "__main__":
    key = generate_key(2048)	# 2048 is the length of RSA key
    pk = key.publickey().export_key()	# for public key
    sk = key.export_key()	# for private key

	message = "We are different, work hard!" * 100
    cipher_text = encrypt(message, pk)
    plaint_text = decrypt(cipher_text, sk)
    print(message == plaint_text)

oeap加密的明文最大长度显然比pkcs要小(pkcs:11个占位,oaep:42个占位)所以在加密同样一段明文时,oaep花的时间要比pkcs更长。

签名之 ---- pkcs

讲道理签名一旦脱离底层,好像没啥好写值得注意的地方。所以直接上代码吧。

from Crypto.PublicKey import RSA
import Crypto.Signature.PKCS1_v1_5 as signer
from Crypto import Hash

def generate_key(bits):
	'''
    :param bits: the length of RSA key
    :return: RSAKey object
    '''
    return RSA.generate(bits)

def sign(plaint_text, sk):
	'''
    :param plaint_text: the plaint text would be signated
    :param sk: the private key
    :return: a signature for the message though sign
    :rtype: bytes
    '''
    signer_obj = signer.new(RSA.importKey(sk))
    hash_obj = Hash.SHA256.new()
    hash_obj.update(plaint_text.encode())
    signature = signer_obj.sign(hash_obj)
    return signature

def verify(signature, plaint_text, pk):
	'''
	:param signature: the signature exited
    :param plaint_text: the plaint text would be signated
    :param pk: the public key
    :return: a signature for the message though sign
    :rtype: boolean
    '''
    verify_obj = signer.new(RSA.importKey(pk))
    hash_obj = Hash.SHA256.new()
    hash_obj.update(plaint_text.encode())
    verify = verify_obj.verify(hash_obj, signature)
    return verify

if __name__ == "__main__":
    key = generate_key(2048)
    pk = key.publickey().export_key()
    sk = key.export_key()

	message = "We are different, work hard!" * 100
	
   	signature = sign(message, sk)
    flag = verify(signature, message, pk)
    print(flag)  

签名之 ---- pss

其实也好像没有啥能说的,但是内部原理也能水一篇博客了,嗯。
代码其实就是把包换成

import Crypto.Signature.PKCS1_PSS as signer

pss由于其有掩码生成函数,所以安全性比pkcs for singnature要强很多,但是由于都是用的hash,所以在时间上并不能感受到差别。

写在最后

oaep搭配pss食用更佳。
欢迎大佬们指正。^ _ ^

猜你喜欢

转载自blog.csdn.net/qq_37195179/article/details/106960468