0

I am using RSA with private/public key. I am trying to encrypt a string, save it in a database (sqlite) and then retrieve it again and encrypt.

I can't decrypt the data again when it is coming out of the sqlite. The string is identical and I am a bit lost.

#!/usr/bin/env python3

from Crypto.PublicKey import RSA
from Crypto import Random
import sqlite3
import base64


# database layout
# 
# CREATE TABLE secrets ( id INT, secret TEXT );
# INSERT INTO secrets (id,secret) VALUES (1,"");

# database
conn = sqlite3.connect('database.db')
c = conn.cursor()

# generate keys
private_key = RSA.generate(1024, Random.new().read)
public_key = private_key.publickey()

# save keys
f = open('public.pem', 'wb+')
f.write(public_key.exportKey('PEM'))
f.close()

f = open('private.pem', 'wb+')
f.write(private_key.exportKey('PEM'))
f.close()

# crypt
f = open('public.pem','rb')
encrypt_public_key = RSA.importKey(f.read())

secret = "123456"

enc_secret = encrypt_public_key.encrypt(secret.encode("utf-8"), 32)[0]
enc_secret_encoded = base64.b64encode(enc_secret)

print("Base64: " + str(enc_secret_encoded))

# save in db
c.execute('UPDATE secrets SET secret="%s" WHERE id=1' % (enc_secret_encoded))
conn.commit()

print("--------------- DECRYPTION ------------------------")

# decrypt
p = open('private.pem','rb')
decrypt_private_key = RSA.importKey(p.read())

c.execute('SELECT secret FROM secrets WHERE id=1')
result = c.fetchone()
encoded_secret = result[0]

print("Base64: " + encoded_secret)

decoded_secret = base64.b64decode(encoded_secret)
enc_secret = decrypt_private_key.decrypt(decoded_secret)

print("Decrypted: " + str(enc_secret))

Output:

$ ./stuck.py
Base64: b'bfAERXPFvrDRdr5Pcexu8JgHlKfDaUhkqJrSWZJbLwlKLWY8XHtIlBwrRfP7eMX9PTKo4t2CtpdXS6Fam4B+jR3/bYPxji0rHt1Aed64sLH4xAnxgh5B/qWidcYT5cPmvwMekGbCaMSgGjvNB4Js/yDRrW4+N8dqx3IoUAl8zgA='
--------------- DECRYPTION ------------------------
Base64: b'bfAERXPFvrDRdr5Pcexu8JgHlKfDaUhkqJrSWZJbLwlKLWY8XHtIlBwrRfP7eMX9PTKo4t2CtpdXS6Fam4B+jR3/bYPxji0rHt1Aed64sLH4xAnxgh5B/qWidcYT5cPmvwMekGbCaMSgGjvNB4Js/yDRrW4+N8dqx3IoUAl8zgA='
Decrypted: b'\x90\x07\xa2}\x96w\xda\xd3h\xf1\xd4\xc6z\xa5\xf3\x85\x97\xeb\xcfL\x0e\x1f;\x18\xd5\x98\xb3\xb2\xd0\x93.\xc9z\x1c\xc8\xac\xe4x\xbfT\xe4{\x1b\x19\xda\xfb/?A\xda_\xceHc\xd14X\x94\x8a\x94\xfc\x12\xc4\x86\xc9\x16\xc9b\xbf\xdaJ\xcf\xff\xe1J\x95\x03&\xda\x98\x9f\x10\xb1\tzW\xea\x9b\xd2\x13\xc1\x8d\x19\xe97\xd6\xeay\xf3\x83\xb7\xcf\xd3v\\`~\x07\xcea(\x81\xe1c\x08\x0b\x8c\xee\xc2\x87\xed\xc8\x08D\x8e\xe5\x83\xf4'

When you run my example, you will see that the same encrypted string gets into the sqlite and out again, but why can't I decrypt it again and get the same result as secret?

UPDATE: When I remove the sqlite database then it works as expected. So the problem must be somewhere in storing or retrieving the data.

Any hint appreciated.

jww
  • 97,681
  • 90
  • 411
  • 885
bert2002
  • 67
  • 1
  • 1
  • 6
  • I don't think you're supposed to use RSA keys to encrypt/decrypt data directly. I can't find `RsaKey.encode` or `Rsa.decode` in [the documentation](https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html). Try using a hybrid RSA-based encryption scheme as shown [here](https://pycryptodome.readthedocs.io/en/latest/src/examples.html#encrypt-data-with-rsa). – Aran-Fey Aug 17 '19 at 08:38
  • For RSA, you will need min 2048-bit security. This will make your data very huge. Why don't you use AES? Also, in your RSA you don't use padding scheme as [PKCS#1.5](https://stackoverflow.com/q/42779158/1820553) which will be vunerable to many attacks. – kelalaka Aug 17 '19 at 13:23
  • @kelalaka I agree to both, this is just an example with less distraction to understand why it is not working. – bert2002 Aug 17 '19 at 15:42

2 Answers2

1

you got the wrong order of base64/RSA while decoding. this works:

#!/usr/bin/env python3

from Crypto.PublicKey import RSA
from Crypto import Random
import base64

key_pair = RSA.generate(1024, Random.new().read(1024 // 8))
public_key = key_pair.publickey()

secret = "123456"

enc_secret = public_key.encrypt(secret.encode("utf-8"), 32)[0]
enc_secret_b64 = base64.b64encode(enc_secret)
print(enc_secret_b64)

enc_secret = base64.b64decode(enc_secret_b64)
secret = key_pair.decrypt(enc_secret)
print(secret.decode("utf-8"))
# 123456

also note that you need to call the .read method of Random.

apart from that: that is not what RSA is meant for. if you want to encrypt data using RSA you should use RSA for key encapsulation only and encrypt the data using a symmetric crypto system (e.g. AES).

hiro protagonist
  • 44,693
  • 14
  • 86
  • 111
  • Hi, thanks for pointint out my missing .read(), but otherwise the code is exactly the same except that no sqlite is used. When I remove the sqlite storage from my code it is also working. – bert2002 Aug 17 '19 at 15:46
  • ooops, you are right... sorry, i seem to have misread... this code decrypts to what it should... maybe you can add `sqlite` and read/write the `pem` and figure out where your code failed. sorry again... – hiro protagonist Aug 17 '19 at 16:15
1

The Python base 64 library returns a bytes object rather than a string when encoding (which is a bit odd since the whole point of base 64 encoding is to create a printable string).

This means that when you convert the result to a string to save it in sqlite it is in the form b'XXX...XX', i.e. it is saved as a string starting with a b with quotes around the actual base64 encoded data.

When decoding, the default is to discard any non base64 characters. So this removes the quotes but not the initial b. This means the data you are decoding has an extra b at the front so you end up trying to decrypt the wrong cipher text.

You can see this by adding validate=True to the call to decode the base 64 data to force it to validate the input. This will cause a binascii.Error because of the ' character.

enc_secret = base64.b64decode(enc_secret_b64, validate=True)

The fix is to decode the bytes object from base 64 encoding into an ASCII string before saving to sqlite. Then only the “real” base 64 characters will saved to the database:

enc_secret_encoded = base64.b64encode(enc_secret).decode("ASCII")
matt
  • 78,533
  • 8
  • 163
  • 197