0

I am implementing a python package which can encrypt and decrypt contents of QR code. I made a module named rsa_module.py, which encrypts and decrypts messages, as follows:

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

def generate_keys(secret_code):
    key = RSA.generate(2048)
    encrypted_key = key.exportKey(passphrase=secret_code, pkcs=8,
                                  protection="scryptAndAES128-CBC")

    output_directory=os.path.dirname(os.path.abspath(__file__))+'/Keys/'
    if(os.path.exists(output_directory)):
        # Save encrypted private key
        file_out = open(output_directory + "/rsa_private_key.pem", "wb")
        file_out.write(encrypted_key)
        # Save public key
        file_out = open(output_directory + "/rsa_public_key.pem", "wb")
        file_out.write(key.publickey().exportKey())
    else:
        os.mkdir(output_directory)
        # Save encrypted private key
        file_out = open(output_directory + "/rsa_private_key.pem", "wb")
        file_out.write(encrypted_key)
        # Save public key
        file_out = open(output_directory + "/rsa_public_key.pem", "wb")
        file_out.write(key.publickey().exportKey())

def encrypt(message):
    output_directory=os.path.dirname(os.path.abspath(__file__))+'/Keys/'

    with open('encrypted_data.txt', 'wb') as out_file:
        recipient_key = RSA.import_key(
        open(output_directory + '/rsa_public_key.pem').read())
        session_key = get_random_bytes(16)
        cipher_rsa = PKCS1_OAEP.new(recipient_key)
        out_file.write(cipher_rsa.encrypt(session_key))

        cipher_aes = AES.new(session_key, AES.MODE_EAX)
        encoded = message.encode("latin-1")
        data = encoded
        ciphertext, tag = cipher_aes.encrypt_and_digest(data)
        out_file.write(cipher_aes.nonce)

        out_file.write(tag)

        out_file.write(ciphertext)

    with open('encrypted_data.txt', 'rb') as fobj:
        output = [l for l in fobj.readlines()]
    os.remove('encrypted_data.txt')
    return output

def decrypt(encrypted_message, secret_code):
    code = secret_code
    output_directory=os.path.dirname(os.path.abspath(__file__))+'/Keys/'

    with open('encrypted_data.txt', 'wb') as temp_file:
        for item in (encrypted_message):
            temp_file.write(item)
    with open('encrypted_data.txt', 'rb') as fobj:
        private_key = RSA.import_key(
        open(output_directory + '/rsa_private_key.pem').read(),
        passphrase=code)
        enc_session_key, nonce, tag, ciphertext = [ fobj.read(x) 
        for x in (private_key.size_in_bytes(), 
        16, 16, -1) ]
        cipher_rsa = PKCS1_OAEP.new(private_key)
        session_key = cipher_rsa.decrypt(enc_session_key)
        cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
        data = cipher_aes.decrypt_and_verify(ciphertext, tag)

    os.remove('encrypted_data.txt')
    return data.decode('utf8')

def main():
    generate_keys('secret one')
    encrypted = encrypt('blah blah blah blo')
    #encrypt_file('blah blah blah blo')
    print('Encryption Complete!')
    print('Decrypting message now....')
    print(encrypted)
    print(decrypt(encrypted, 'secret one'))
    #decrypt_file('secret one')

if __name__=='__main__': main()

If I run this script then the message is being encrypted and decrypted successfully. But when I use the same functions in another module, which decrypts the message from the QR code, I get an error. The name of this qr code decryption model is decrypt_qr.py, and the code for it as follows:

from qrtools import qrtools
from PIL import Image
import zbarlight
import os
from rsa_module import decrypt as de

def decrypt(file_name, password):
    keys_directory=os.path.dirname(os.path.abspath(__file__))+'/Keys/'
    private_key_path = keys_directory + '/rsa_private_key.pem'

    if(os.path.exists(private_key_path)):
        output_directory=os.path.dirname(os.path.abspath(__file__))+'/Output/'
        file_path = output_directory + file_name + '.PNG'
        with open(file_path, 'rb') as image_file:
            image = Image.open(image_file)
            image.load()
        codes = zbarlight.scan_codes('qrcode', image)
        decoded_result=codes[0].decode('utf8')
        print(codes[0].decode('utf8'))
        return de(decoded_result, password)
    else:
        print('No Public key available. Generate Public key and Private key first.')
        return None

def main():
    print(decrypt('my-qr','My secret'))

if __name__=='__main__': main()

If I run the decrypt_qr.py, I receive the following error:

Traceback (most recent call last):   
  File "decrypt_qr.py", line 28, in <module>
    if __name__=='__main__': main()   
  File "decrypt_qr.py", line 26, in main
    print(decrypt('my-qr','My secret'))   
  File "decrypt_qr.py", line 20, in decrypt
    return de(decoded_result, password)   
  File "/Users/king/Documents/pyWorkspace/Encrypted_QR_Code_Using_AES/rsa_module.py", line 93, in decrypt
    temp_file.write(item)
TypeError: a bytes-like object is required, not 'str'

But if I run the rsa_module.py with just the message being passed then it does decrypt properly. Can anyone suggest where I am going wrong?

The encryption module, named encrypt_qr.py, is as follows:

from generate_qr import make_qr_and_save
from rsa_module import encrypt as en
from rsa_module import generate_keys
import os

def encrypt(message, filename, password, size=3):
    generate_keys(password)
    keys_directory=os.path.dirname(os.path.abspath(__file__))+'/Keys/'
    public_key_path = keys_directory + '/rsa_public_key.pem'

    if(os.path.exists(public_key_path)):
        encrypted_message = en(message)
        print('\n')
        print(encrypted_message)
        make_qr_and_save(encrypted_message, filename, size)
    else:
        print('No Public key available. Generate Public key and Private key first.')
        return None

def main():
    encrypt('Buzz off!','my-qr','My secret')

if __name__=='__main__': main()

If I run the encrypt_qr.py script then the qr code is generated properly and contains encrypted byte stream, which is then used in the decryption script (decrypt_qr.py).

computingfreak
  • 4,939
  • 1
  • 34
  • 51
  • `item` might be a string in `temp_file.write(item)`. So try changing `wb to w` in `with open('encrypted_data.txt', 'w') as temp_file:` – Stack Mar 10 '18 at 17:07
  • I actually tried that but if that is the case then in decrypt() function of rsa_module.py, I am actually sending in the encrypted message as byte stream and it can decrypt that properly. That's why it just doesnt make sense to me. How can one code work for one module but not for the other when the input type is the same. – Moody Cody Mar 10 '18 at 17:12

1 Answers1

0

You are passing in text, decoded from UTF-8:

decoded_result=codes[0].decode('utf8')
# ...
return de(decoded_result, password)   

but your decrypt() function expects this to be bytes, because you opened the file in binary mode:

def decrypt(encrypted_message, secret_code):
    # ...
    with open('encrypted_data.txt', 'wb') as temp_file:
        for item in (encrypted_message):
            temp_file.write(item)  

Pass in a sequence of bytes instead, not a single str object:

decoded_result = codes[0]
# ...
return de([decoded_result], password)   

Note that (encrypted_message) in for item in (encrypted_message): is the same thing as encrypted_message without parentheses. The (...) are only serving to group the element, no tuple is created. If you were to pass in a single bytes object, you'd be iterating over a sequence of integers (representing the individual bytes in the object).

Also, there is no need to write the data to a file on disk. You could use a io.BytesIO() object for an in-memory file, or just slice up the message in the right block sizes. Using a file on disk here actually makes the code more complicated than it is, and you hardcoded the /tmp path (different OS-es use different paths, the tempfile module can abstract this for you).

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Martijn, Thank you first of all. But if i get rid of the .decode('utf8') and just use decoded_result = codes[0], then I receive the following error: TypeError: a bytes-like object is required, not 'int' – Moody Cody Mar 10 '18 at 17:14
  • @MoodyCody: yes, because you iterate over `encrypted_message`, which gives you individual bytes *as integers*. Note that I pass in a list of bytes, not a single bytes object. – Martijn Pieters Mar 10 '18 at 17:16
  • thank you a ton. I just tried that and got a different error, which I have updated in the question. – Moody Cody Mar 10 '18 at 17:26
  • @MoodyCody: that's a new issue though. Please stick to one problem at a time. – Martijn Pieters Mar 10 '18 at 17:28