As already mentioned in my comment, encrypt and decrypt of PyCryptodome can only be used to encrypt with the public key and decrypt with the private key. PyCryptodome has no 1:1-counterpart to private_encrypt or public_decrypt of M2Crypto, which allows the encryption with the private key and the decryption with the public key. Instead PyCryptodome uses sign and verify, which however work differently in detail, so that private_encrypt and sign don't generate the same signature (for the same key and message):
sign implements RSASSA-PKCS1-V1_5 padding described in RFC 8017, chapter 8.2. The hash value H of the message is padded as follows:
0x00 || 0x01 || PS || 0x00 || ID || H
ID identifies the digest and is for SHA1 (see here for other digests):
(0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14
PS are 0xFF fill bytes, so that the padded message has the length of the modulus.
The padding of private_encrypt differs from RSASSA-PKCS1-V1_5 padding in such a way that ID is not added automatically. So that sign and private_encrypt generate the same signature, ID must be added manually in the context of private_encrypt, e.g:
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)
As a site note, there is a bug in the original code: private_encrypt expects the data in binary format and not as hexadecimal string.
The corresponding PyCryptodome code could be e.g.:
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())
With the following test key (for simplicity a 512 bit key):
key = """-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2gdsVIRmg5IH0rG3
u3w+gHCZq5o4OMQIeomC1NTeHgxbkrfznv7TgWVzrHpr3HHK8IpLlG04/aBo6U5W
2umHQQIDAQABAkEAu7wulGvZFat1Xv+19BMcgl3yhCdsB70Mi+7CH98XTwjACk4T
+IYv4N53j16gce7U5fJxmGkdq83+xAyeyw8U0QIhAPIMhbtXlRS7XpkB66l5DvN1
XrKRWeB3RtvcUSf30RyFAiEA5ph7eWXbXWpIhdWMoe50yffF7pW+C5z07tzAIH6D
Ko0CIQCyveSTr917bdIxk2V/xNHxnx7LJuMEC5DcExorNanKMQIgUxHRQU1hNgjI
sXXZoKgfaHaa1jUZbmOPlNDvYYVRyS0CIB9ZZee2zubyRla4qN8PQxCJb7DiICmH
7nWP7CIvcQwB
-----END PRIVATE KEY-----"""
both codes provide for the following message:
payload_xml = b'The quick brown fox jumps over the lazy dog'
the following signature:
8324a560e6934fa1d1421b9ae37641c3b50a5c3872beecea808fbfed94151747aad69d5e083a23aa0b134d9e8c65e3a9201bb22ec28f459e605692e53965ad3b
Conclusion: It is possible to modify the M2Crypto code so that the result corresponds to the PyCryptodome code by simply adding ID. The other way around, however, it doesn't seem to be possible, because the PyCryptodome implementation adds ID automatically and this apparently can't be prevented.
encrypt- anddecrypt-methods of the PyCryptodomeCipher-class only allow encryption with the public key and decryption with the private key, i.e. PyCryptodome has no 1:1 counterparts to theprivate_encrypt- andpublic_decrypt-methods of M2Crypto you use (instead PyCryptodome has the sign- and verify-methods of theSignature-class). In the Python 3.6 code, therfore, only the SHA1-hash of the payload is encrypted using the public key (even if the private key is applied, only the portion with the public key is used). This is pure encryption and no signing.