python-3.xencryption

python 3 encrypt and decrypt image using AES


I am using AES to encrypt and decrypt the image. I have inherited the code so please guide me if you guys see something wrong. I am trying to understand and fix the code.

    chunk_size = 64*1024
    output_file = filename+".enc"
    file_size = bytes(os.path.getsize(filename))
    IV = get_random_bytes(16)

    encryptor = AES.new(key, AES.MODE_CBC, IV)
    with open(filename, 'rb') as inputfile:
        with open(output_file, 'wb') as outf:
            outf.write(file_size)
            outf.write(IV)
            while True:
                chunk = bytes(inputfile.read(chunk_size))
                if len(chunk) == 0:
                    break
                elif len(chunk) % 16 != 0:
                   chunk = chunk + bytes(16 - len(chunk)%16)
                outf.write(encryptor.encrypt(chunk))

My image is 66 kb which comes around - 67584 bytes. Considering AES works with 16 bytes this code should error out but it generate the encrypted file. When I try to decrypt using

def decrypt(key, filename):
        chunk_size = 64*1024
        output_file = filename[:-4]
        with open(filename, 'rb') as inf:
            filesize = bytes(inf.read(16))
            IV = inf.read(16)
            decryptor = AES.new(key, AES.MODE_CBC, IV)
            with open(output_file, 'wb') as outf:
                while True:
                    chunk = inf.read(chunk_size)
                    print(len(chunk))
                    if len(chunk)==0:
                        break
                    outf.write(decryptor.decrypt(chunk))
                outf.truncate(filesize)```

I get an error like below

TypeError: an integer is required (got type bytes)

and when I type chunk in bytes I get the following error

Input strings must be a multiple of 16 in length

I am confused how can I fix error "multiple of 16 in length" when my filesize on the console for source is shown as 65536.


Solution

  • The file size is stored improperly. To store the file size in the first 16 bytes (as it's presumably intended with regard to the decrypt method, although 16 bytes are actually too large) in big endian order, replace in the encryption:

    file_size = bytes(os.path.getsize(filename))
    

    with

    file_size = os.path.getsize(filename).to_bytes(16, byteorder='big')
    

    and in the decryption:

    filesize = bytes(inf.read(16))
    

    with

    filesize = int.from_bytes(inf.read(16), byteorder='big')
    

    With these changes, encryption and decryption work as intended.

    Note: You use a Zero padding variant for padding and store the file size (probably only) to remove the padding after decryption. There is a more efficient method, PKCS7 padding. Here the information how many bytes to remove is already included in the padding itself. So the file size does not have to be stored (at least not to remove the padding). In addition, padding is also supported in PyCryptodome by the methods pad and unpad.