3
\$\begingroup\$

This code hides a message in an image by altering the blue values in the top left part. This is just for fun as I think it would be easy for a cryptographer to find and decrypt this.

Original

enter image description here

With message We will meet at London bridge at dawn

enter image description here

As you can (cannot) see, as long as the message is short enough, the naked eye has difficulty noticing the difference. Zooming in the top left corner will reveal the trick though.

from PIL import Image
from itertools import zip_longest
import doctest

def read_and_write_after_tranformation(infile, outfile, transformation):
    # Based on a StackOverflow answer.
    im = Image.open(infile)
    list_of_pixels = list(im.getdata())
    im2 = Image.new(im.mode, im.size)
    im2.putdata(transformation(list_of_pixels))
    im2.save(outfile)

def read_rgb(infile):
    return list(Image.open(infile).getdata())

def steganographic_encrypt(message, rgb_tuples):
    """
    Encodes a message in the blue values of the pixels on the top left corner of the image.

    >>> steganographic_encrypt("Hello, World!", [(a, b, c, luminosity) for a, b, c, luminosity in zip(range(12, 30), range(20, 48), range(100, 128), range(100, 128))])
    [(12, 20, 72, 100), (13, 21, 101, 101), (14, 22, 108, 102), (15, 23, 108, 103), (16, 24, 111, 104), (17, 25, 44, 105), (18, 26, 32, 106), (19, 27, 87, 107), (20, 28, 111, 108), (21, 29, 114, 109), (22, 30, 108, 110), (23, 31, 100, 111), (24, 32, 33, 112), (25, 33, 113, 113), (26, 34, 114, 114), (27, 35, 115, 115), (28, 36, 116, 116), (29, 37, 117, 117)]
    """
    return [(red, green, blue if ascii_val is None else ascii_val, luminosity)
              for (red, green, blue, luminosity), ascii_val in zip_longest(rgb_tuples, map(ord, message))]

def steganographic_decrypt(rgb_tuples):
    """
    Decodes a message from the blue values of the pixels on the top left corner of the image.

    >>> steganographic_decrypt([(12, 20, 72, 100), (13, 21, 101, 101), (14, 22, 108, 102), (15, 23, 108, 103), (16, 24, 111, 104), (17, 25, 44, 105), (18, 26, 32, 106), (19, 27, 87, 107), (20, 28, 111, 108), (21, 29, 114, 109), (22, 30, 108, 110), (23, 31, 100, 111), (24, 32, 33, 112), (25, 33, 113, 113), (26, 34, 114, 114), (27, 35, 115, 115), (28, 36, 116, 116), (29, 37, 117, 117)])
    'Hello, World!qrstu'
    """
    return ''.join([chr(blue) for (red, green, blue, luminosity) in rgb_tuples])

if __name__ == "__main__":
    doctest.testmod()
    read_and_write_after_tranformation(
        "cc.png",
        "cc2.png",
        lambda image: steganographic_encrypt("We will meet at London bridge at dawn", image)
    )

    print(steganographic_decrypt(read_rgb("cc2.png"))[0:100])
\$\endgroup\$
  • 1
    \$\begingroup\$ Note that steganography and encryption are different things. That being said, a message could be encrypted prior to being embedded into an image using a steganographic technique. \$\endgroup\$ – code_dredd Sep 5 '15 at 10:53
4
\$\begingroup\$

Working with resources

In read_and_write_after_tranformation you didn't bother to close any of the image resources:

# Based on a StackOverflow answer.
im = Image.open(infile)
list_of_pixels = list(im.getdata())
im2 = Image.new(im.mode, im.size)
im2.putdata(transformation(list_of_pixels))
im2.save(outfile)

Don't believe everything you read on Stack Overflow =)

You can wrap those in a with ... as. And while at it, im and im2 are not great names.

with Image.open(infile) as in_img:
    list_of_pixels = list(in_img.getdata())
    with Image.new(in_img.mode, in_img.size) as out_img:
        out_img.putdata(transformation(list_of_pixels))
        out_img.save(outfile)

Usability

This is a nice script. It would be great to make it more usable, with command line parameters:

  • hide sub-command: message to hide, input file, output file
  • show sub-command: file

You probably already know this, but argparse would be a good tool for this job.

\$\endgroup\$
  • \$\begingroup\$ How would you handle in the command line args that this script can both encrypt and decrypt? \$\endgroup\$ – Caridorc Sep 5 '15 at 11:17
  • \$\begingroup\$ You have a point. See my update. \$\endgroup\$ – janos Sep 5 '15 at 11:20
  • \$\begingroup\$ I tried, but it continues crashing and the error seems obviously wrong, may you take a look and give me a nudge in the right direction, please? codepaste.net/65zz1i \$\endgroup\$ – Caridorc Sep 5 '15 at 11:47
  • \$\begingroup\$ You'll need something like args = parser.parse_args(), and then work with that args object, rather than parser_show.infile \$\endgroup\$ – janos Sep 5 '15 at 12:12
  • \$\begingroup\$ I think it is better for me to give up writing this parser. I would have never though that it was so hard to write a multi-sub-argument parser. Two scripts one for encryption the other for decryption should work just as well right? \$\endgroup\$ – Caridorc Sep 5 '15 at 12:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.