7

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")
Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
kilokahn
  • 1,136
  • 2
  • 18
  • 38

1 Answers1

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.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • 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? – kilokahn Jun 27 '17 at 23:08
  • 1
    [`keys.publickey()`](http://legrandin.github.io/pycryptodome/Doc/3.4.6/Crypto.PublicKey.RSA._RSAobj-class.html#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.* – Maarten Bodewes Jun 27 '17 at 23:11
  • 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! – kilokahn Jun 27 '17 at 23:19
  • 1
    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`](http://legrandin.github.io/pycryptodome/Doc/3.4.6/Crypto.PublicKey.RSA-module.html#generate) you will see it generates a single *`key`* object, not *`keys`* object. – Maarten Bodewes Jun 27 '17 at 23:26
  • That was insightful. Thank you so much for sharing. :) – kilokahn Jun 27 '17 at 23:27
  • 1
    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). – Maarten Bodewes Jun 27 '17 at 23:28
  • So in essence - I can export it, but good luck to me if I try using that key to decode something. 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. – kilokahn Jun 27 '17 at 23:34
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/147766/discussion-between-kilokahn-and-maarten-bodewes). – kilokahn Jun 27 '17 at 23:40