Let's say you want to send a short private message to your friend, but don't want grandma to snoop around and find it. One way is to use the Python Image Library (PIL) and hide the message in a picture file's pixels. Just looking at the picture you will barely detect any difference between the before and after picture. All your friend needs is the decode_image portion of this simple code to get the message back.

Edited 2 Years Ago by vegaseat: RGBA

Comments
Great!
Pretty damn cool
''' PIL_HideText1.py
hide a short message (255 char max) in an image
the image has to be .bmp or .png format
and the image mode has to be 'RGB'
'''

from PIL import Image

def encode_image(img, msg):
    """
    use the red portion of an image (r, g, b) tuple to
    hide the msg string characters as ASCII values
    red value of the first pixel is used for length of string
    """
    length = len(msg)
    # limit length of message to 255
    if length > 255:
        print("text too long! (don't exeed 255 characters)")
        return False
    if img.mode != 'RGB':
        print("image mode needs to be RGB")
        return False
    # use a copy of image to hide the text in
    encoded = img.copy()
    width, height = img.size
    index = 0
    for row in range(height):
        for col in range(width):
            r, g, b = img.getpixel((col, row))
            # first value is length of msg
            if row == 0 and col == 0 and index < length:
                asc = length
            elif index <= length:
                c = msg[index -1]
                asc = ord(c)
            else:
                asc = r
            encoded.putpixel((col, row), (asc, g , b))
            index += 1
    return encoded

def decode_image(img):
    """
    check the red portion of an image (r, g, b) tuple for
    hidden message characters (ASCII values)
    """
    width, height = img.size
    msg = ""
    index = 0
    for row in range(height):
        for col in range(width):
            try:
                r, g, b = img.getpixel((col, row))
            except ValueError:
                # need to add transparency a for some .png files
                r, g, b, a = img.getpixel((col, row))		
            # first pixel r value is length of message
            if row == 0 and col == 0:
                length = r
            elif index <= length:
                msg += chr(r)
            index += 1
    return msg


# pick a .png or .bmp file you have in the working directory
# or give full path name
original_image_file = "Beach7.png"
#original_image_file = "Beach7.bmp"

img = Image.open(original_image_file)
# image mode needs to be 'RGB'
print(img, img.mode)  # test

# create a new filename for the modified/encoded image
encoded_image_file = "enc_" + original_image_file

# don't exceed 255 characters in the message
secret_msg = "this is a secret message added to the image"
print(len(secret_msg))  # test

img_encoded = encode_image(img, secret_msg)

if img_encoded:
    # save the image with the hidden text
    img_encoded.save(encoded_image_file)
    print("{} saved!".format(encoded_image_file))

    # view the saved file, works with Windows only
    # behaves like double-clicking on the saved file
    import os
    os.startfile(encoded_image_file)
    '''
    # or activate the default viewer associated with the image
    # works on more platforms like Windows and Linux
    import webbrowser
    webbrowser.open(encoded_image_file)
    '''
    # get the hidden text back ...
    img2 = Image.open(encoded_image_file)
    hidden_text = decode_image(img2)
    print("Hidden text:\n{}".format(hidden_text))

I downloaded the encode picture from above and applied this little code:

''' PIL_HideText_decode.py
get the hidden text back from an encoded image
'''

from PIL import Image

def decode_image(img):
    """
    check the red portion of an image (r, g, b) tuple for
    hidden message characters (ASCII values)
    """
    width, height = img.size
    msg = ""
    index = 0
    for row in range(height):
        for col in range(width):
            try:
                r, g, b = img.getpixel((col, row))
            except ValueError:
                # need to add transparency a for some .png files
                r, g, b, a = img.getpixel((col, row))
            # first pixel r value is length of message
            if row == 0 and col == 0:
                length = r
            elif index <= length:
                msg += chr(r)
            index += 1
    return msg

# downloaded from internet web page
encoded_image_file = "Beach7_enc.png"

# get the hidden text back ...
img2 = Image.open(encoded_image_file)
print(img2, img2.mode)  # test
hidden_text = decode_image(img2)
print("Hidden text:\n{}".format(hidden_text))

''' my result -->
Hidden text:
this is a secret message added to the image
'''

It works!

Hai
I want to convert this code into gimp plugin in python language.Their is no security plugin exits in gimp. Please help me to convert the above code into a plugin . After applying plugin i want to view the hidden secret msg in the image. Please help me

image steganography plugin in python

from gimpfu import *
#import math, random, os, sys

def do_message_box(msg):
    handler = pdb.gimp_message_get_handler()
    pdb.gimp_message_set_handler(MESSAGE_BOX)   #{ MESSAGE-BOX (0), CONSOLE (1), ERROR-CONSOLE (2) }
    pdb.gimp_message(msg)
    pdb.gimp_message_set_handler(handler)

def hide_image(image, layer, carrier, radix):

    if layer.width > carrier.width or layer.height > carrier.height:
    do_message_box("Carrier image must be at least as large as the image to be hidden!!")
    return

    s = int(radix)

    pdb.gimp_image_undo_group_start(image)
    newlayer = pdb.gimp_layer_new_from_drawable(carrier, image)
    image.add_layer(newlayer, -1)

    gimp.progress_init("Hiding image...")

    ntx = int( (layer.width+63)/64 )            # number of tiles horizontally
    nty = int( (layer.height+63)/64 )           # number of tiles vertically
    for i in range(ntx):
    for j in range(nty):                    # for each tile...
        srcTile = layer.get_tile(False, j, i)
        carTile = carrier.get_tile(False, j, i)
        dstTile = newlayer.get_tile(False, j, i)
        for x in range(srcTile.ewidth):         # for each pixel in tile..
        for y in range(srcTile.eheight):
            spixel = srcTile[x,y]
            cpixel = carTile[x,y]
            r = min(255, ord(cpixel[0]) - (ord(cpixel[0]) % s) + int(s * ord(spixel[0]) / 256))
            g = min(255, ord(cpixel[1]) - (ord(cpixel[1]) % s) + int(s * ord(spixel[1]) / 256))
            b = min(255, ord(cpixel[2]) - (ord(cpixel[2]) % s) + int(s * ord(spixel[2]) / 256))
            dstTile[x,y] = chr(r) + chr(g) + chr(b)
    gimp.progress_update(float(i)/ntx)

    newlayer.flush()
    newlayer.update(0, 0, newlayer.width, newlayer.height)

    pdb.gimp_image_resize_to_layers(image)
    pdb.gimp_image_undo_group_end(image)
    gimp.displays_flush() 

def recover_image(image, layer, radix):

    s = int(radix)

    pdb.gimp_image_undo_group_start(image)
    newlayer = layer.copy()
    image.add_layer(newlayer, -1)

    gimp.progress_init("Recovering image...")

    ntx = int( (layer.width+63)/64 )            # number of tiles horizontally
    nty = int( (layer.height+63)/64 )           # number of tiles vertically
    for i in range(ntx):
    for j in range(nty):                    # for each tile...
        srcTile = layer.get_tile(False, j, i)
        dstTile = newlayer.get_tile(False, j, i)
        for x in range(srcTile.ewidth):         # for each pixel in tile..
        for y in range(srcTile.eheight):
            pixel = srcTile[x,y]
            r = (ord(pixel[0]) % s) * 256 / s
            g = (ord(pixel[1]) % s) * 256 / s
            b = (ord(pixel[2]) % s) * 256 / s
            dstTile[x,y] = chr(r) + chr(g) + chr(b)
    gimp.progress_update(float(i)/ntx)

    newlayer.flush()
    newlayer.update(0, 0, newlayer.width, newlayer.height)

    pdb.gimp_image_undo_group_end(image)
    gimp.displays_flush() 

register(
    "python_fu_simple_steg_hide_image",
    "Hides an RGB image in another RGB image",
    "Hides an RGB image in another RGB image",
    "",
    "",
    "Version 1.0 (2-4-2015)",
    "Hide image",
    "RGB",
    [
      (PF_IMAGE,    "image",    "Input image", None),
      (PF_DRAWABLE, "drawable", "Input layer", None),
      (PF_DRAWABLE, "carrier",  "Carrier image", None),
      (PF_SPINNER,  "radix",    "Radix", 4, (2, 64, 1))
    ],
    [],
    hide_image,
    menu="<Image>/MyScripts/Steg/Image Steganography")

register(
    "python_fu_simple_steg_recover_image",
    "Recovers a 'hidden image' from an RGB image",
    "Recovers a 'hidden image' from an RGB image",
    "",
    "",
    "Version 1.0 (2-4-2015)",
    "Recover image",
    "RGB",
    [
      (PF_IMAGE,    "image",    "Input image", None),
      (PF_DRAWABLE, "drawable", "Input layer", None),
      (PF_SPINNER,  "radix",    "Radix", 4, (2, 64, 1))
    ],
    [],
    recover_image,
    menu="<Image>/MyScripts/Steg/Image Steganography")

main()

*

Edited 1 Year Ago by jismy jose

text steganography plugin

from gimpfu import *

def do_message_box(msg):
    handler = pdb.gimp_message_get_handler()
    pdb.gimp_message_set_handler(MESSAGE_BOX)   #{ MESSAGE-BOX (0), CONSOLE (1), ERROR-CONSOLE (2) }
    pdb.gimp_message(msg)
    pdb.gimp_message_set_handler(handler)

def add_steg_text(image, layer, text):

    length = len(text)
    # limit length of message to 255
    if length > 255:
        do_message_box("text too long! (don't exeed 255 characters)")
        return

    pdb.gimp_image_undo_group_start(image)
    newlayer = layer.copy()
    image.add_layer(newlayer, -1)
    pr = newlayer.get_pixel_rgn(0,0,newlayer.width,newlayer.height)

    for index in range(length+1):
        col = (index) / layer.height
        row = (index) % layer.height
        if index == 0:              #store length in first pixel
            newpixel = chr(length)
        else:                   #store string in subsequent pixels
            newpixel = text[index-1]
        newpixel += pr[col,row][1] + pr[col,row][2]
        if layer.has_alpha:
            newpixel += pr[col,row][3]
        pr[col,row] = newpixel 

    newlayer.flush()
    layer.merge_shadow(True)
    newlayer.update(0, 0, newlayer.width, newlayer.height)

    pdb.gimp_image_undo_group_end(image)
    gimp.displays_flush() 

def read_steg_text(image, layer, outputmode):

    pr = layer.get_pixel_rgn(0,0,layer.width,layer.height)
    length = ord(pr[0,0][0])
    msg = ""
    for index in range(length):
        col = (index+1) / layer.height
        row = (index+1) % layer.height
        msg += pr[col,row][0]

    if outputmode == 0:
        do_message_box("%s" %msg)

register(
    "python_fu_simple_steg_text_add",
    "Adds a simple 'hidden message' to an RGB image",
    "Adds a simple 'hidden message' to an RGB image",
    "",
    "",
    "Version 1.0 (25-3-2015)",
    "Add message",
    "RGB*",
    [
      (PF_IMAGE,    "image",    "Input image", None),
      (PF_DRAWABLE, "drawable", "Input layer", None),
      (PF_STRING,   "text",     "Text:",       "")
    ],
    [],
    add_steg_text,
    menu="<Image>/MyScripts/Steg/Text Steganography")

register(
    "python_fu_simple_steg_text_read",
    "Reads a simple 'hidden message' added to an RGB image",
    "Reads a simple 'hidden message' added to an RGB image",
    "",
    "",
    "Version 1.0 (25-3-2015)",
    "Display message",
    "RGB*",
    [
      (PF_IMAGE,    "image",    "Input image", None),
      (PF_DRAWABLE, "drawable", "Input layer", None),
      (PF_OPTION,   "outputmode", "Output mode", 0, ("Display in a message box", "None"))
    ],
    [],
    read_steg_text,
    menu="<Image>/MyScripts/Steg/Text Steganography")

main()
The article starter has earned a lot of community kudos, and such articles offer a bounty for quality replies.