XOR PNG script


After seeing a thread on reddit the other day I wanted to make a little script to XOR encrypt a PNG image file, and still being able to view the image. The script encrypt the pixel values o the PNG and stores the resulting image. It makes it very visually clear that choosing a good key when encrypting something is important. The pictures below shows a few different pictures encrypted with several different keys.


Unencrypted picture of the astronaut.

Astronaut picture encrypted with the key "a".Can still clearly see the astronaut.

Astronaut picture encrypted with the key long key. Can still clearly see the astronaut.

Here the picture is encrypted with a key that is over 600 chars long, and you can still clearly see the astronaut in the picture. A small pattern has emerged in the picture compared to the image above it which was only encrypted with the key "a".

The astronaut picture encrypted with a one time pad. Can no longer see any resemblance.

This is the astronaut picture, and all visual resemblance with the previous pictures is gone.

Pinkie Pie unencrypted.

Pinkie Pie encrypted with the key "a" and still looking as happy as always.

Pinkie Pie encrypted with a long key, but still clearly visible, but picture has a small pattern on it.

Here Pinkie Pie is encrypted with the same long key (over 600 chars) as the astronaut picture. The small pattern is not as strong in this picture.

Drawing of the grumpy cat unencrypted.

Drawing of the grumpy cat encrypted with the key "a". The cat is still clearly visible.


    Idea spawned from a reddit thread: 

    It XORs the inputted image either with a one time pad, or a user specified key. 
    It cleary visualizes why strong keys are needed for encryption.

    Do not actually use this to encrypt stuff for privacy. It only changes pixel values, and
    not metadata, and the image header.

    Sindre Smistad <sindre@downgoat.net>

    import sys
    import random
    import argparse
    from PIL import Image

    MODES = ["RGB", "RGBA"]

    op = False

    def xor(pixel, mode, key):
        #XOR all the channels with the same key.
        red = pixel[0] ^ key[0]
        green = pixel[1] ^ key[1]
        blue = pixel[2] ^ key[2]

        if mode == "RGBA": #Need Aplha if RGBA
            return (red, green, blue, 255)
            return (red, green, blue)

    parser = argparse.ArgumentParser(description="XOR-viewer")
    parser.add_argument("pic_name", action="store", help="Name of picture file")
    parser.add_argument("out_pic", action="store", help="Name of the output file.")
    parser.add_argument("-op", help="Use one time pad.", action="store_true")
    parser.add_argument("-k", help="Encryption key", action="store")

    args = parser.parse_args()

    #Raise exception if both k and op is set, don't know which one to use then.
    if args.op and args.k:
        raise ValueError("Cannot use both one time pad and a key.")

    #If none is set go with one time pad.
    if not args.op and not args.k:
        print("No key specified, will use one time pad.")
        op = True

    if args.op:
        op = True

    im = Image.open(args.pic_name)

    #If the image mode is not in the list of supported modes, rase a exception.
    if im.mode not in MODES:
        raise NotImplementedError("The image mode '{0}' is not supported.".format(im.mode))

    key = []
    org_len = 0

    pix = im.load()
    width, height = im.size

    if op:
        #Number of keys needed is pixel in the image times 3 because each channel needs a key.
        for x in range(0, width*height*3): 
            key.append(random.SystemRandom().randint(0, 255))

        org_len = len(key)
        #Key needs to be atleast 3.
        temp = ""
        if len(args.k) < 3:
            temp  = args.k + args.k
            if len(temp) < 3:
                temp += args.k
            temp = args.k

        for l in temp:

        org_len = len(key)

        #Key robin hack.

    key_count = 0
    for x in range(0, width):
        for y in range(0, height):
            if key_count >= org_len:
                key_count = 0

            pix[x, y] = xor(pix[x, y], im.mode, (key[key_count], key[key_count+1], key[key_count+2]))

            key_count += 3