RSA decryption of AES Session key fails with 'AttributeError: 'bytes' object has no attribute 'n'
Asked Answered
B

1

8

I'm working on implementing a public key encryption from PyCryptodome on Python 3.6. When I try to create a symmetric encryption key and encrypt/decrypt variables, it all works fine. But the minute I introduce RSA (and PKCS1_OAEP), it all goes down the tubes - the session_key encrypts fine but when I try and decrypt it, I get the following error:

Traceback (most recent call last):
  File "enctest.py", line 109, in <module>
    deckey = decrypt_val(enckey)
  File "enctest.py", line 77, in decrypt_val
    session_key = cipher.decrypt(ciphertext)
  File "/usr/lib/python3.6/site-packages/Crypto/Cipher/PKCS1_OAEP.py", line 187, in decrypt
    modBits = Crypto.Util.number.size(self._key.n)
AttributeError: 'bytes' object has no attribute 'n'

My code is as follows. Can anyone take a look and tell me what I'm doing wrong?

from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto import Random
from Crypto.Random import get_random_bytes

random_generator = Random.new().read
keys = RSA.generate(1024, random_generator)
pubkey = keys.publickey()
privkey = keys.exportKey()
pubcipher = PKCS1_OAEP.new(pubkey) # ciphertext = cipher.encrypt(message)
privcipher = PKCS1_OAEP.new(privkey)  # message = cipher.decrypt(ciphertext)
privkeystr = keys.exportKey(format='PEM', passphrase=None, pkcs=1)
pubkeystr = keys.publickey().exportKey(format='PEM', passphrase=None, pkcs=1)

def encrypt_val(session_key, cipher = pubcipher):
    try:
        session_key = session_key.encode('utf8')
    except:
        pass
    ciphertext = cipher.encrypt(session_key)
    print("encrypted key : %s \n" % ciphertext)
    return ciphertext


def decrypt_val(ciphertext, cipher = privcipher):
    session_key = cipher.decrypt(ciphertext)
    try:
        session_key = session_key.decode('utf8')
    except:
        pass
    return session_key

def aesenc(data):
    try:
        data = data.encode('utf8')
    except:
        pass
    key = get_random_bytes(16)
    cipher = AES.new(key, AES.MODE_EAX)
    ciphertext, tag = cipher.encrypt_and_digest(data)
    aesencdict = {'aesdict' : {'ciphertext' : ciphertext, 'tag' : tag, 'nonce' : cipher.nonce} , 'key' : key}
    return(aesencdict)

def aesdec(aesdict, key):
    cipher = AES.new(key, AES.MODE_EAX, aesdict['nonce'])
    data = cipher.decrypt_and_verify(aesdict['ciphertext'], aesdict['tag'])
    try:
        data = data.decode('utf8')
    except:
        pass
    return data


val = "hello"
encval = aesenc(val)
enckey = encrypt_val(encval['key'])
print(enckey)
deckey = decrypt_val(enckey)
print(deckey)
if deckey == encval['key']:
    outval = aesdec(encval['aesdict'], encval['key'])
    print(val, outval)
else:
    print("oops\n")
Bokbokhara answered 27/6, 2017 at 22:22 Comment(2)
Thanks for the formatting @Maarten Bodewes :)Bokbokhara
You're welcome, I hope you like my answer as well :) Note, I haven't tried it yet, but it seems correct to me and it was a bit long for a comment.Matty
M
7

It seems you do a spurious export, which translates a key into the encoding of a key:

privkey = keys.exportKey()
....
privcipher = PKCS1_OAEP.new(privkey)  # message = cipher.decrypt(ciphertext)

after which it tries to find the modulus n from the encoded key instead of from the object instance that contains a member n.

Try:

privcipher = PKCS1_OAEP.new(keys)

instead.

Matty answered 27/6, 2017 at 23:3 Comment(8)
OMG it worked. Thank you so much @Maarten Bodewes ! So I shouldn't try and export the private key before I PKCS1_OAEP'ify it? Can you help me understand why that works for the public key and not the private key?Bokbokhara
keys.publickey() just separates the public key information from the private key object; it doesn't actually encode it. You need to call exportKey again for that. Note that you should use pkcs=8 to export the private key.Matty
Yes, I understand that, but I did also call privkey = keys.exportKey() - so that only exports the encoding of the key and doesnt work the same way as the Public Key separation? So is there a way to separate the Private Key component? Thanks again!Bokbokhara
You don't ever need to separate the private key component. It only contains the public exponent (i.e. the number 65537, the fourth prime of Fermat, called F4 usually) in addition to the private exponent and all the CRT parameters (when present). The public exponent is usually encoded within the private key as well - it's public after all. If you look at generate you will see it generates a single key object, not keys object.Matty
That was insightful. Thank you so much for sharing. :)Bokbokhara
By the way, I was wrong about the private key, you can export as PKCS#1 encoding as well, but that encoding does not contain info that it is indeed an RSA private key. Most software I work with accept PKCS#8 encoded keys rather than PKCS#1 encoded keys (the PKCS#1 encoding is embedded in the PKCS#8 encoding, to be precise).Matty
<Oversimplification> So in essence - I can export it, but good luck to me if I try using that key to decode something. </oversimplification> So its better I just use the PKCS1_OAEP.new(keys) method. What would you suggest I do if I need to, say, store the private key in a (separate, offline, non-application server based) database? I would rather not export it to a file and then store that in the database as a blob, if that can be helped.Bokbokhara
Let us continue this discussion in chat.Bokbokhara

© 2022 - 2024 — McMap. All rights reserved.