The Artima Developer Community
Sponsored Link

Python Buzz Forum
Steganography made simple

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Thomas Guest

Posts: 236
Nickname: tag
Registered: Nov, 2004

Thomas Guest likes dynamic languages.
Steganography made simple Posted: Jan 10, 2009 3:10 AM
Reply to this message Reply

This post originated from an RSS feed registered with Python Buzz by Thomas Guest.
Original Post: Steganography made simple
Feed Title: Word Aligned: Category Python
Feed URL: http://feeds.feedburner.com/WordAlignedCategoryPython
Feed Description: Dynamic languages in general. Python in particular. The adventures of a space sensitive programmer.
Latest Python Buzz Posts
Latest Python Buzz Posts by Thomas Guest
Latest Posts From Word Aligned: Category Python

Advertisement

What's hidden in this image?

As programmers, our code should be readable, not cryptic; but sometimes it’s fun to surprise, obfuscate or conceal. Wikipedia says:

Steganography is the art and science of writing hidden messages in such a way that no-one apart from the sender and intended recipient even realizes there is a hidden message. By contrast, cryptography obscures the meaning of a message, but it does not conceal the fact that there is a message.

Lemon juice on paper never worked for me, and (as I discovered when I tried to devise a title for an earlier post) it’s hard work hiding a text message as a pattern within a larger text message. Sadly my hair is too fine for Histiaeus’s inspired shave-a-slave trick.

In a digital age, steganographers have it easier. The larger the carrier message the easier it is to disguise a payload within it. My mobile phone has a 3 megapixel camera; I could embed the entire text content of this website (tarred and bzipped) within a single one of its photos without anyone noticing. The Wikipedia page on steganography has a remarkable example of a picture of a tree which, after some bit-shifting, turns into a passable picture of a cat!

The Python Imaging Library (PIL) makes tinkering with images a snip. Here’s a short program to hide messages in images, and to reveal such messages. PIL isn’t ready for Python 3.0 yet, so I’m using 2.6. Note in passing the use of a couple of recent additions to my favourite module, itertools.product and itertools.izip_longest.

PIL steganography
'''Digital image steganography based on the Python Imaging Library (PIL)

Any message can be hidden, provided the image is large enough. The message is
packed into the least significant bits of the pixel colour bands. A 4 byte
header (packed in the same way) carries the message payload length.
'''
import Image
import itertools as its

def n_at_a_time(items, n, fillvalue):
    '''Returns an iterator which groups n items at a time.
    
    Any final partial tuple will be padded with the fillvalue
    
    >>> list(n_at_a_time([1, 2, 3, 4, 5], 2, 'X'))
    [(1, 2), (3, 4), (5, 'X')]
    '''
    it = iter(items)
    return its.izip_longest(*[it] * n, fillvalue=fillvalue)

def biterator(data):
    '''Returns a biterator over the input data.
    
    >>> list(biterator(chr(0b10110101)))
    [1, 0, 1, 1, 0, 1, 0, 1]
    '''
    return ((ord(ch) >> shift) & 1
            for ch, shift in its.product(data, range(7, -1, -1)))

def header(n):
    '''Return n packed in a 4 byte string.'''
    bytes = (chr(n >> s & 0xff) for s in range(24, -8, -8))
    return ('%s' * 4) % tuple(bytes)

def setlsb(cpt, bit):
    '''Set least significant bit of a colour component.'''
    return cpt & ~1 | bit

def hide_bits(pixel, bits):
    '''Hide a bit in each pixel component, returning the resulting pixel.'''
    return tuple(its.starmap(setlsb, zip(pixel, bits)))

def hide_bit(pixel, bit):
    '''Similar to the above, but for single band images.'''
    return setlsb(pixel, bit[0])

def unpack_lsbits_from_image(image):
    '''Unpack least significant bits from image pixels.'''
    # Return depends on number of colour bands. See also hide_bit(s)
    if len(image.getbands()) == 1:
        return (px & 1 for px in image.getdata())
    else:
        return (cc & 1 for px in image.getdata() for cc in px)

def call(f): # (Used to defer evaluation of f)
    return f()

def disguise(image, data):
    '''Disguise data by packing it into an image.
    
    On success, the image is modified and returned to the caller.
    On failure, None is returned and the image is unchanged.
    '''
    payload = '%s%s' % (header(len(data)), data)
    npixels = image.size[0] * image.size[1]
    nbands = len(image.getbands())
    if len(payload) * 8 <= npixels * nbands:
        new_pixel = hide_bit if nbands == 1 else hide_bits
        pixels = image.getdata()
        bits = n_at_a_time(biterator(payload), nbands, 0)
        new_pixels = its.starmap(new_pixel, its.izip(pixels, bits))
        image.putdata(list(new_pixels))
        return image

def reveal(image):
    '''Returns any message disguised in the supplied image file, or None.'''
    bits = unpack_lsbits_from_image(image)
    def accum_bits(n):
        return reduce(lambda a, b: a << 1 | b, its.islice(bits, n), 0)
    def next_ch():
        return chr(accum_bits(8))
    npixels = image.size[0] * image.size[1] 
    nbands = len(image.getbands())
    data_length = accum_bits(32)
    if npixels * nbands > 32 + data_length * 8:
        return ''.join(its.imap(call, its.repeat(next_ch, data_length)))

if __name__ == "__main__":
    import urllib
    droste = urllib.urlopen("http://is.gd/cHqT").read()
    open("droste.png", "wb").write(droste)
    droste = Image.open("droste.png")
    while droste:
        droste.show()
        droste = reveal(droste)
        if droste:
            open("droste.png", "wb").write(droste)
            droste = Image.open("droste.png")

The code is available via anonymous SVN access at http://svn.wordaligned.org/svn/etc/steganography.

☡ For brevity, I haven’t provided a nice user interface to disguise() and reveal(). The short main program is (intentionally) lightly obfuscated. Disguise() modifies the supplied image argument — use Image.copy() if this is a problem. You must also choose a lossless format to save the disguised image: I recommend PNG, but please do check reveal() works on any saved image.


Keen on quines and cocoa?

Read: Steganography made simple

Topic: Twitter Weekly Updates for 2009-01-09 Previous Topic   Next Topic Topic: 2009 Personal Goals

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use