2

I want to encrypt data in an image but the resulting ciphertext to still be a valid image. I encrypt the image with AES in python and then, I replace header in the files, but windows can't open the encrypted image.

Code

def encrypt_file(self, in_filename, out_filename):
    filesize = os.path.getsize(in_filename)
    iv = Random.new().read(AES.block_size)
    cipher = AES.new(self.key, AES.MODE_ECB, iv)
    chunksize = 64 * 1024
    with open(in_filename, 'rb') as infile:
        with open(out_filename, 'wb') as outfile:
            outfile.write(struct.pack('<Q', filesize))
            outfile.write(iv)

            while True:
                chunk = infile.read(chunksize)
                if len(chunk) == 0:
                    break
                elif len(chunk) % 16 != 0:
                    chunk += ' ' * (16 - len(chunk) % 16)
                cifrado = base64.b64encode(cipher.encrypt(chunk))
                print cifrado
                outfile.write(cipher.encrypt(chunk))

I want this effect: The ECB Penguin

A.J. Uppal
  • 19,117
  • 6
  • 45
  • 76
davidad
  • 137
  • 1
  • 10
  • Could you provide some more information to this? Maybe some code? Your title could be a bit more representative too – koukouviou Mar 13 '15 at 18:48
  • what image format and image viewer are you trying to use; the linked article used ppm, which is a nice simple format as opposed to e.g. jpeg... though I don't know if windows comes with a ppm reader by default. – Foon Mar 14 '15 at 02:11

2 Answers2

5

PyCrypto and the Python Image Class had very useful examples on playing around with images and the AES encryption.

This implementation only works with BMP images that have certain characteristics. The main characteristic that an image must have for this solution to work is that it has to be of a size that's multiple of 16 bytes (for the encryption part, AES ECB operates on 16 byte chunks). You can work on improving this to accept more image formats and pad to 16 byte multiples :)

If you don't provide an image, an appropriate image from the web is automatically downloaded.

im_show from Image is known to cause problems on some platforms. I tested this on an Ubuntu 14.10 distro and didn't run into any problems. This was tested on Python 2.7, I'm still working on my portability skills (You didn't specify a Python version in your question so...)

#!/usr/bin/python
import binascii, os.path, urllib, random, Image
from Crypto.Cipher import AES

class ECBPenguin(object):
    '''
    A penguin class
    '''
    def __init__(self, img_clr=""):
        if not img_clr:
            self.__demo_image__()
            self.img_clr = "tux_clear.bmp"
        else:
            self.img_clr = img_clr
        self.__get_header__()

    def __demo_image__(self):
        ''' 
        Downloads a TUX image compatible for this program: square and with size multiple of 16
        '''
        print "Downloading image..."
        image = urllib.URLopener()
        image.retrieve("http://fp-games.googlecode.com/svn/trunk/CodeWeek1/graviTux/data/tux.bmp","tux_clear.bmp") 

    def __get_sizes__(self, dibheader):
        # Get image's dimensions (at offsets 4 and 8 of the DIB header)
        DIBheader = []
        for i in range(0,80,2):
            DIBheader.append(int(binascii.hexlify(dibheader)[i:i+2],16))
        self.width = sum([DIBheader[i+4]*256**i for i in range(0,4)])
        self.height = sum([DIBheader[i+8]*256**i for i in range(0,4)])

    def __get_header__(self):
        '''
        Read BMP and DIB headers from input image and write them to output image
        '''
        f_in = open(self.img_clr, 'rb')
        # BMP is 14 bytes
        bmpheader = f_in.read(14)
        # DIB is 40 bytes
        dibheader = f_in.read(40)
        self.__get_sizes__(dibheader)
        self._bmpheader = bmpheader
        self._dibheader = dibheader
        f_in.close()

    def encrypt(self, img_enc = "tux_enc.bmp", key = '0123456789abcdef'):
        '''
        Encrypt the my_penguin
        '''
        self.img_enc = img_enc
        f_in = open(self.img_clr, 'rb')
        f_out = open(img_enc, 'wb')
        f_out.write(self._bmpheader)
        f_out.write(self._dibheader)
        row_padded = (self.width * self.height * 3)
        image_data = f_in.read(row_padded)
        cleartext =  binascii.unhexlify(binascii.hexlify(image_data))

        # Initialization Vector
        IV = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
        # AES ECB mode
        mode = AES.MODE_ECB
        # Encryptor
        encryptor = AES.new(key, mode, IV=IV)
        # Perform the encryption and write output to file
        f_out.write(encryptor.encrypt(cleartext))
        f_in.close()
        f_out.close()

    def show_clr(self):
        '''
        Display cleartext penguin
        '''
        im = Image.open(self.img_clr)
        im.show()

    def show_enc(self):
        '''
        Display ciphertext penguin
        '''
        im = Image.open(self.img_enc)
        im.show()

def main():
    my_penguin = ECBPenguin()
    my_penguin.show_clr()
    my_penguin.encrypt()
    my_penguin.show_enc()

if __name__ == "__main__":
    main()

The initial and encrypted images look like this:

Plaintext TUX ECB "Encrypted" TUX

I couldn't find the same image as the one in your link, but the point of weakness of ECB is still made!

koukouviou
  • 820
  • 13
  • 23
1

In simple terms:

  1. Grab the original image in .BMP format.

  2. Keep the original BMP header unencrypted.

  3. Encrypt just the image, not the header.

  4. Put the original unencrypted header back in front of the encrypted image.

You may need to tweak the header a little if the encryption has added a few padding bytes to the image size.

rossum
  • 15,344
  • 1
  • 24
  • 38
  • thanks, but this information i know. i want code or Pseudocode. – davidad Mar 18 '15 at 10:01
  • Which of the four steps do you have difficulties with? "Pseudocode" covers a great many possibilities. My answer could be seen as very high level Pseudocode. – rossum Mar 18 '15 at 12:25
  • How to extract the header from the image and how to put it back after encryption , can you suggest? – user311790 Mar 23 '20 at 18:45
  • Read up on the [BMP file format](https://en.wikipedia.org/wiki/BMP_file_format). You will need to partially parse the file to locate the pixel array which holds the image. – rossum Mar 23 '20 at 19:54