如何解码使用私钥进行 RSA 编码的 128 字节字符串。它遵循 PKCS#7 标准,但没有哈希机制

问题描述 投票:0回答:2

我有一个用私钥编码的 128 字节字符串(十六进制)。我知道这样做是为了确保只有那些拥有私钥的人才能创建这个字符串(然后添加到二维条码)。我有一个 X509 PEM 格式的公共证书,可以从中提取模数和指数。

数据使用私钥加密,使用1024Bit RSA PKCS#1v1.5。这可以保护最多 116 字节或 928 位的有效负载,从而创建 128 字节或 1024 位的加密输出。

pyCryptodome 似乎专门防止使用公钥解码。据我所知,这实际上是数字签名而不是加密,但我需要解码字符串,而不仅仅是确认字符串已使用该证书进行编码。

当我尝试在 Python 中创建代码时,出现字符串太长的错误。

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from base64 import b64decode
import binascii

#exponent = '65537'
exponent = '10001'

#msg= b'9254A06EBF59BDD4DF6565CDBE94CFA8DD8E540ADC0812C2CFE75A06006304AF30158CD6F00AC52AB32CB464EFD690EE096BE2722613D6E2212161950716D209746081DF5186682480B0E6AD2F1E5F2798DDB082AAA344C1DF8FEC70697FEE3D6E77D16AFECB0566A4590B926B8461DF47CC65CA102C83025469246D7B164EAE'
msg= b'9254A06EBF59BDD4DF6565CDBE94CFA8DD8E540ADC0812C2CFE75A06006304AF30158CD6F00AC52AB3'
# Modulus extracted from certificate
modulus = 'DE8CA25087EC1FF6103DA3BDB7A8F960AF93ABFD1B1F5EBEBE88E77885AD5BFC8D4759B79EFE0173B50FD96AC2B05124AE5CC2DBBA1BC804FA80D9EEB1CC547F39E5524D704CACACFFE235E87744E2F0A7660BDB8694B3D84CAB18D71A2593BBF5BC39F7FF67547477803B8B8EBDD390AEB63F742A081AF947C0E85A69DBE3EB'

# create a key with the modulus and exponent
rsaKey = RSA.construct((int(modulus,16), int(exponent,16)))
pubKey = rsaKey.publickey()

# decrypt the message using the public key
decryptor=PKCS1_OAEP.new(pubKey)
decrypted=decryptor.encrypt(msg)

print("Decrypted:", binascii.hexlify(decrypted))

如果我缩短消息的长度,流程可以正常运行,但消息现在不正确。编码后的原始字符串为 116 个字节。

如果我更改代码:

decrypted=decryptor.encrypt(msg)

decrypted=decryptor.decrypt(msg)

我明白了

“长度错误的密文”。 k 为 128,密文长度为 256 hLen = 20。要使其正常工作,密文长度和 k 应相等或 k < hlen+2.

python encryption rsa digital-signature pycryptodome
2个回答
1
投票

您的数据确实似乎是通过用 RSAES-PKCS1-v1_5 填充消息生成的(即在加密上下文中填充)然后用私钥加密它(在模幂的意义上与私有指数).

这种组合是不一致的:以保密为目的用私钥加密是没有意义的,因为公钥是可以访问的,任何人都可以解密。对于签名,RSASSA-PKCS1-v1_5 必须用作填充。

由于这些不一致,使用

PKCS1_v1_5#decrypt()
的 PyCryptodome 解密是不可能的(这需要私钥)。
然而,可能的是使用公钥解密(在使用公共指数进行模幂运算的意义上)和随后用户定义的 RSAES-PKCS1-v1_5 填充删除。
后者的形式为0x00 || 0x02 ||附言|| 0x00 || MPS:非零字节序列),因此消息结果为第二个 0x00 字节之后的字节序列。

解密本质上是按照

verify()
方法的实现,unpadding在第二步处理:

from Crypto.PublicKey import RSA
from Crypto.Util import number

signature = bytes.fromhex('9254A06EBF59BDD4DF6565CDBE94CFA8DD8E540ADC0812C2CFE75A06006304AF30158CD6F00AC52AB32CB464EFD690EE096BE2722613D6E2212161950716D209746081DF5186682480B0E6AD2F1E5F2798DDB082AAA344C1DF8FEC70697FEE3D6E77D16AFECB0566A4590B926B8461DF47CC65CA102C83025469246D7B164EAE')

exponent = '10001'
modulus = 'DE8CA25087EC1FF6103DA3BDB7A8F960AF93ABFD1B1F5EBEBE88E77885AD5BFC8D4759B79EFE0173B50FD96AC2B05124AE5CC2DBBA1BC804FA80D9EEB1CC547F39E5524D704CACACFFE235E87744E2F0A7660BDB8694B3D84CAB18D71A2593BBF5BC39F7FF67547477803B8B8EBDD390AEB63F742A081AF947C0E85A69DBE3EB'

# Import the public key
rsaKey = RSA.construct((int(modulus,16), int(exponent,16)))
pubKey = rsaKey.publickey()

# Step 1: Decrypt
signature_int = number.bytes_to_long(signature) 
msgPadded_int = pubKey._encrypt(signature_int)
msgPadded = number.long_to_bytes(msgPadded_int, pubKey.size_in_bytes())

# Step 2: Unpad
m_start = 2 + msgPadded[2:].index(0) + 1 # 0x00 || 0x02 || PS || 0x00 || M.
msg = msgPadded[m_start:]

# Output
print(pubKey.export_key('PEM').decode())
print(msgPadded.hex()) # 0002060106030504010207000041041041045045206b7579ed92a8a8c2a82a8aa8a8c00000000b49363409a400000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e588a62d74b3734a
print(msg.hex()) # 0041041041045045206b7579ed92a8a8c2a82a8aa8a8c00000000b49363409a400000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e588a62d74b3734a
print(len(msg)) # 116

请注意,任何公钥(所需大小)都可以在步骤 1 中解密密文而不会出现任何技术错误,因为没有执行解填充操作。
不正确的解密看起来像随机字节序列。由于此处找到的消息与此类消息明显不同,因此结果应该是正确的(长度与预期长度相符也支持这一点)。
另一方面,PS (0x060106030504010207) 似乎不是 RSAES-PKCS1-v1_5 中指定的随机字节序列。


0
投票

您可以通过子类化密钥并进行

_decrypt
调用
_encrypt
.

来欺骗库使用公钥解密
from Crypto.PublicKey.RSA import RsaKey
from Crypto.Cipher import PKCS1_v1_5
from Crypto.Math.Numbers import Integer

class DecryptingPublicKey(RsaKey):
    def _decrypt(self, ciphertext):
        return self._encrypt(ciphertext)

msg = "9254A06EBF59BDD4DF6565CDBE94CFA8DD8E540ADC0812C2CFE75A06006304AF30158CD6F00AC52AB32CB464EFD690EE096BE2722613D6E2212161950716D209746081DF5186682480B0E6AD2F1E5F2798DDB082AAA344C1DF8FEC70697FEE3D6E77D16AFECB0566A4590B926B8461DF47CC65CA102C83025469246D7B164EAE"
modulus = "DE8CA25087EC1FF6103DA3BDB7A8F960AF93ABFD1B1F5EBEBE88E77885AD5BFC8D4759B79EFE0173B50FD96AC2B05124AE5CC2DBBA1BC804FA80D9EEB1CC547F39E5524D704CACACFFE235E87744E2F0A7660BDB8694B3D84CAB18D71A2593BBF5BC39F7FF67547477803B8B8EBDD390AEB63F742A081AF947C0E85A69DBE3EB"
exponent = "10001"

msg_bin = bytes.fromhex(msg)
pubKey = DecryptingPublicKey(
    n=Integer(int(modulus, 16)),
    e=Integer(int(exponent, 16))
)
decryptor = PKCS1_v1_5.new(pubKey)
decrypted = decryptor.decrypt(msg_bin, b"")
print("Decrypted", len(decrypted), decrypted)

解密 116 b'\x00A\x04\x10A\x04PE kuy\xed\x92\xa8\xa8\xc2\xa8*\x8a\xa8\xa8\xc0\x00\x00\x00\x0bI64 \xa4\x00\x00\ x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe5\ x88\xa6-t\xb3sJ'

注意:由于这依赖于内部未记录的功能,因此在未来(>3.17)版本中可能无法使用。

© www.soinside.com 2019 - 2024. All rights reserved.