0

I am using the following code to encode and decode an image using AES in ECB mode. Once the image is decoded it is supposed to look exactly the same as the original. However, there is a problem with the operation because the images are not the same (though it is visually not possible to see a difference). The original image is 19kb but the decoded image is 397 kb. Any insight on what I need to change to make the images the same size will be highly appreciated.

    from PIL import Image, ImageFile
    from Cryptodome.Cipher import AES
    from io import BytesIO
    
    filename = "jessica.bmp"
    filename_out = "1_enc_img"
    
    key = "aaaabbbbccccdddd"
    ImageFile.LOAD_TRUNCATED_IMAGES = True
    
    def pad(data):
        bytearray_data = bytearray()
        l = 0
        for i in range(1, len(data), 1):
            bytearray_data += bytearray(data[l: i] + b"\x00" * (16 - len(data[l: i]) % 16))
            l = i
        return bytearray_data
    
    def process_image(filename):
        with open(filename, 'rb') as f:
            data = f.read()
        img_bytes = aes_ecb_encrypt(key, pad(data))
        
        f = open(filename_out, 'wb')
        f.write(img_bytes)
    
    def aes_ecb_encrypt(key, data, mode=AES.MODE_ECB):
        aes = AES.new(key.encode("utf8"), mode)
        new_data = aes.encrypt(data)
        return new_data
    
    def create_dictionary():
        dictionary = bytearray()
        for i in range(256):
            dictionary += bytearray(i.to_bytes(16, byteorder='little'))
        
        return dictionary
    
    def encrypt_dict(dictionary):
        return aes_ecb_encrypt(key, dictionary)
    
    def decode_image():
        with open('./dict', 'rb') as f:
            dictionary = f.read()
        im = open('./1_enc_img', 'rb')
        data2 = im.read()
        dict = {}
        c = 0
        l = 0
        dict_file = open('./dict.txt', 'a')
        for i in range(16, len(dictionary) + 1, 16):
            temp_dict = {(dictionary[l: i]): c.to_bytes(1, byteorder='little')}
            dict.update(temp_dict)
            
            dict_file.write("{} - {}\n".format(temp_dict, c))
            c += 1
            l = i
        c = 0
        l = 0
        result = bytearray()
        for i in range(16, len(data2), 16):
            
            result += dict[data2[l: i]]
            c += 1
            l = i
        stream = BytesIO(result)
        image = Image.open(stream).convert("RGBA")
        stream.close()
        picture = image.save("{}.bmp".format("decoded_image"))
    
    
    def main():
        f = open('./dict', 'wb')
        f.write(encrypt_dict(create_dictionary()))
        f.close()
        process_image(filename)
        decode_image()
    
    main()

Gabzxx_23
  • 13
  • 3

1 Answers1

1

As far as I can see you are:

  • reading in a BMP
  • encrypting it
  • saving it to disk
  • reloading it from disk
  • decrypting it
  • saving as a BMP

However, you didn't share the original, or the recreated, resaved image. So I can only guess...

The original image may have been a very efficiently saved palette image (a.k.a index image) which only needs one byte per pixel (rather than 3 bytes of RGB) and uses that byte as an index to look up a full 24-bit RGB colour in a palette of 256 colours.

You then encrypted (fine), decrypted (fine) but then arbitrarily decided to insist on a full 32-bit per pixel RGBA output file when you did:

image = Image.open(stream).convert("RGBA")

The best way to check is to run:

exiftool ORIGINAL.BMP
exiftool DECRYPTED.BMP

and see what the differences are - I guess the original will be a palette image and the decrypted will be 4-channel RGBA which can't hope to compete.


Of course, you could open your input image, convert it to RGBA and save it without any encryption/decryption and compare sizes...

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432