前言:网上有很多关于使用pyCryptodome库进行RSA加解密以及签名,但是大部分都是根据该库的开发文档进行的,只能算是最基础的版本。我们知道,RSA是明文分组加密,也就是说,它的加密的消息是定长的。但是现实场景中,我们大部分遇上的都是不定长消息,而超长消息在现实中更为常见。所以如何切割与填充,这是在实际应用中值得关注的问题所在。
观前提醒
本博客代码是查阅和分析pyCryptodome库关于RSA源码后,写的实际调用的代码。采用的是python语言编写,如果是Java,实际上也可以参考思路进行改造。
该博客写作之前,已经将分析源码的PPT上传,客官可移步: 基于RSA的PyCryptodome库讲解.pptx.(为卑微的一级菜鸡捐献一点点积分)
网上的大部分代码都是遵循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_en
和length_de
是不一样的。最主要的原因是message最少要留出11个比特去进行填充,解密的时候就只需要分组密码字符长度就可。也就是说,当你的message很短,ps可以动态填充到分组密码长度,但是ps最少要8个比特。然后pkcs的解密过程如果出现错误,错误返回的信息可以由你自己定(比如上方的'DecryptError'
),这样就不用try-except啦。
在上方之前讲到,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
except:
return "解密失败"
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更长。
讲道理签名一旦脱离底层,好像没啥好写值得注意的地方。所以直接上代码吧。
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)
其实也好像没有啥能说的,但是内部原理也能水一篇博客了,嗯。
代码其实就是把包换成
import Crypto.Signature.PKCS1_PSS as signer
pss由于其有掩码生成函数,所以安全性比pkcs for singnature要强很多,但是由于都是用的hash,所以在时间上并不能感受到差别。
写在最后
oaep搭配pss食用更佳。
欢迎大佬们指正。^ _ ^