How do I port python2 code using RSA.importKey().decrypt() to python3?

43 views Asked by At

We have a legacy python2 application that uses an RSA private key to decrypt a base64-encoded file. How do we port this to python3?

Python2.7 code, this does work:

    def _unlock_keys(self, passphrase):
        #get user priv key passphrase and perform unlock of randbits
        master_key = None
        master_key_path = 'master.pem'

        with open(master_key_path) as f:
            master_key = RSA.importKey(f.read(), passphrase)

        with open('host_keys/part.A') as f:
            enc_bits = f.read()
            self.part_A = master_key.decrypt(b64decode(enc_bits))
            
        # self.part_A has the data we need

Python3 code, this does not work:

This is what we've tried on the python3 code, but so far it decrypts to the empty bytestring b'':

    def _unlock_keys(self, passphrase):
        #get user priv key passphrase and perform unlock of randbits
        master_key = None 
        master_key_path = 'master.pem'

        with open(master_key_path) as f:
            master_key = RSA.importKey(f.read(), passphrase)

        with open('host_keys/part.A') as f:
            enc_bits = f.read()

            # Note: PKCS1_OAEP doesn't work because this is raw, unpadded:
            decryptor = PKCS1_v1_5.new(master_key)
            self.part_A = decryptor.decrypt(b64decode(enc_bits), "error")
            print(self.part_A)

        # self.part_A prints as b''

OpenSSL command that works

We can use OpenSSL to decrypt it as follows. Note the -raw argument because there is no PKCS padding, and this is PKCS v1.5:

openssl rsautl -pkcs -raw -decrypt -in <(base64 -d host_keys/part.A) -out /proc/self/fd/1 -inkey master.pem
Enter pass phrase for master.pem:
<correct output>

(Maybe I should have posed the question "how can I implement this openssl command in python3?" but methinks that would be an XY question...)

Key format

In case it helps, this is what the private key format looks like.

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,<REDACTED>

...
-----END RSA PRIVATE KEY-----
1

There are 1 answers

0
BoppreH On

I cannot reproduce the result being empty, but the fact that you were doing raw decryption in Python 2.7 and PKCS#1 v1.5 decryption in Python 3 is definitely an issue. Those are not compatible, and I'm surprised you didn't get errors (or maybe you did and swallowed them?).

Raw RSA encryption is so vulnerable it's practically useless, but in the interest of migrating your data, here's how you do raw decryption in newer versions of Crypto (now available as pip install pycryptodome):

#### COMPLETELY INSECURE ENCRYPTION, DO NOT USE TO PROTECT DATA ####

from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.PublicKey import RSA

rsa_key = RSA.generate(2048)
plaintext = b'attack at dawn'
ciphertext = long_to_bytes(rsa_key._encrypt(bytes_to_long(plaintext)))
# This is the line that you need:
decrypted = long_to_bytes(rsa_key._decrypt(bytes_to_long(ciphertext)))
assert decrypted == plaintext

#### COMPLETELY INSECURE ENCRYPTION, DO NOT USE TO PROTECT DATA ####

Note that the inputs and outputs must be numbers, so we need bytes_to_long and long_to_bytes.