在Python 3.X中实现RSA/pkcs1_padding

4

我正在尝试将我的代码从Python 2.7迁移到Python 3.5

以下是当前在Python 2.7中使用M2Crypto的实现

import M2Crypto
import hashlib
from binascii import hexlify

# Generates the signature of payload
def getSign(payload_xml):
    # SHA-1 digest of the payload
    dig = myDigest(payload_xml)
    # Loading the privateKey PEM file
    private_key = M2Crypto.RSA.load_key('privatekey')
    # Generating base 16 and encoding
    signature = hexlify(private_key.private_encrypt(dig, M2Crypto.RSA.pkcs1_padding))
    return signature

# To generate sha-1 digest of payload
def myDigest(payload):

    # This will give base 16 of SHA-1 digest
    digest_1 = hashlib.sha1(payload).hexdigest()
    return digest_1

sign = getSign(<mypayload_xml>)

这是使用pycryptodomePython 3.5中的新实现。

from Crypto.PublicKey import RSA
import hashlib
from Crypto.Cipher import PKCS1_v1_5
from binascii import hexlify

def myDigest(payload):
    # This will give base 16 of SHA-1 digest
    digest_1 = hashlib.sha1(payload.encode('utf-8')).hexdigest()
    return digest_1

def getSign(payload_xml):
    # SHA-1 digest of the payload
    dig = myDigest(payload_xml)
    with open('privatekey', 'r') as pvt_key:
        miPvt = pvt_key.read()
    rsa_key_obj = RSA.importKey(miPvt)
    cipher = PKCS1_v1_5.new(rsa_key_obj)
    cipher_text = cipher.encrypt(dig.encode())
    base_16_new = hexlify(cipher_text)
    return base_16_new

new_sign = getSign(<mypayload_xml>)

然而,对于相同的有效负载,签名是不同的。有人能帮忙提供正确的解决方案吗?

据我所知,PyCryptodome Cipher 类的 encryptdecrypt 方法只允许使用公钥进行加密和私钥进行解密,即 PyCryptodome 没有与您使用的 M2Crypto 的 private_encryptpublic_decrypt 方法一一对应的方法(而是 PyCryptodome 具有 Signature 类的签名和验证方法)。因此,在 Python 3.6 代码中,仅使用公钥加密负载的 SHA1 哈希值(即使应用了私钥,也仅使用公钥部分)。这是纯加密而不是签名。 - Topaco
2个回答

3
正如我在评论中提到的那样,PyCryptodome中的encryptdecrypt只能用于使用公钥加密和使用私钥解密。PyCryptodome没有与M2Crypto的private_encryptpublic_decrypt完全对应的方法, M2Crypto的这两个方法允许使用私钥进行加密并使用公钥进行解密。相反,PyCryptodome使用signverify,但它们在细节上有所不同,因此private_encryptsign 不会生成相同的签名(对于相同的密钥和消息):

sign实现了RFC 8017第8.2章中描述的RSASSA-PKCS1-V1_5填充。消息的哈希值H的填充方式如下:

0x00 || 0x01 || PS || 0x00 || ID || H

ID 用于标识摘要,适用于 SHA1(参见 此处 中的其他摘要):

(0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 

PS是填充字节,值为0xFF,以使填充后的消息长度等于模长。


private_encrypt的填充方式与RSASSA-PKCS1-V1_5不同,不会自动添加ID。因此,在private_encrypt中,需要手动添加ID以使signprivate_encrypt生成相同的签名,例如:

import M2Crypto
import hashlib
from binascii import hexlify, unhexlify

key = """-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----"""

def getSign(payload_xml):
    dig = myDigest(payload_xml)
    private_key = M2Crypto.RSA.load_key_string(key)
    signature = hexlify(private_key.private_encrypt(unhexlify('3021300906052b0e03021a05000414' + dig.hexdigest()), M2Crypto.RSA.pkcs1_padding))
    return signature

def myDigest(payload_xml):
    digest_1 = hashlib.sha1(payload_xml)
    return digest_1 

sign = getSign(b"Hello world")
print("M2Crypto: " + sign)

作为一个提示,原始代码存在一个错误: private_encrypt 需要以二进制格式而不是十六进制字符串的形式提供数据。
相应的 PyCryptodome 代码可以如下所示:
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA1
from Crypto.PublicKey import RSA

key = """-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----"""

def getSign(payload_xml):
    dig = myDigest(payload_xml)
    private_key = RSA.import_key(key)
    signature = pkcs1_15.new(private_key).sign(dig)
    return signature

def myDigest(payload_xml):
    digest_1 = SHA1.new(payload_xml)
    return digest_1 

sign = getSign(b'Hello world')
print("PyCryptodome: " + sign.hex())

使用以下测试密钥(为简单起见,使用512位密钥):

key = """-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2gdsVIRmg5IH0rG3
u3w+gHCZq5o4OMQIeomC1NTeHgxbkrfznv7TgWVzrHpr3HHK8IpLlG04/aBo6U5W
2umHQQIDAQABAkEAu7wulGvZFat1Xv+19BMcgl3yhCdsB70Mi+7CH98XTwjACk4T
+IYv4N53j16gce7U5fJxmGkdq83+xAyeyw8U0QIhAPIMhbtXlRS7XpkB66l5DvN1
XrKRWeB3RtvcUSf30RyFAiEA5ph7eWXbXWpIhdWMoe50yffF7pW+C5z07tzAIH6D
Ko0CIQCyveSTr917bdIxk2V/xNHxnx7LJuMEC5DcExorNanKMQIgUxHRQU1hNgjI
sXXZoKgfaHaa1jUZbmOPlNDvYYVRyS0CIB9ZZee2zubyRla4qN8PQxCJb7DiICmH
7nWP7CIvcQwB
-----END PRIVATE KEY-----"""

两个代码均提供以下信息:

payload_xml = b'The quick brown fox jumps over the lazy dog'

以下是签名:

8324a560e6934fa1d1421b9ae37641c3b50a5c3872beecea808fbfed94151747aad69d5e083a23aa0b134d9e8c65e3a9201bb22ec28f459e605692e53965ad3b

结论: 可以通过简单添加“ID”来修改M2Crypto代码,使结果与PyCryptodome代码相对应。然而,反过来似乎不可能,因为PyCryptodome实现自动添加“ID”,并且这显然不能被防止。

0
在第二个代码片段中,你正在使用PKCS#1 1.5算法的加密SHA-1摘要(Crypto.Cipher.PKCS1_v1_5模块)。那不是一个签名。
相反,你应该使用pycryptodomeCrypto.Signature.pkcs1_15模块。例如,请参见here中的示例。
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA

message = 'To be signed'
key = RSA.import_key(open('private_key.der').read())
h = SHA256.new(message)
signature = pkcs1_15.new(key).sign(h)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接