Steganography

Source Code Walkthrough

Most of the art in building Steganography applications resides in the encoding algorithm. Though before you can conjure up various encoding methods, you need to build a decent framework in which to place the encoding method.

A framework for Steganography applications is fairly straightforward, this source code walkthrough will concern itself with the basic framework of a Steganography application. Python will be the language and development environment for the module. The end result should be a basic Steganography library.

Overview

A Steganography library servers two main purposes; either to encode or decode a message.

    Main purposes of a Steganography library:
  1. Encode a Message from PlainInfo.
  2. Decode a Message from StegData.

Steganography Overview

A Steganography library could be split into separate encoding and decoding modules. Though the encoding and decoding methods are often inverse functions of each other.

Encoding Input -> Process -> Output

PlainInfo is an input, this is normally derived from clearInfo. CoverData is another input, though the minimum amount of CoverData is related to the amount of plainInfo and the process of encoding to be used.

The process is one of encoding the plainInfo with the coverData. The coverData will normally have to be prepared in some way to encode the data.

The output is the resulting stegData.

    Inputs
  1. PlainInfo
  2. CoverData
    Process
  1. Check coverData amount against plainInfo and encode method.
  2. Encode plainInfo with coverData.
    Output
  1. StegData

Steganography Encode (Input Process Output)

Decoding Input -> Process -> Output

StegData is the input, the decode method has to relate to the encode method of the StegData.

The process is to decode the StegData.

PlainInfo should then result as the output.

    Input
  1. StegData
    Process
  1. Decode StegData
    Output
  1. PlainInfo

Steganography Decode (Input Process Output)

Use Cases

Two main use cases appear with Steganography; encoding and deconding.

This code walkthrough is designed to produce a library that will allow basic Steganography. ClearInfo to PlainInfo and vice versa functions can be added, as well as loading ClearInfo, loading CoverData and saving StegData.

The user case will take into account these extras, and a separate class will be developed to take them into account.

When encoding the user will be able to, give a file name of a text file to be used as ClearInfo, or enter ClearInfo as text directly, give a file name of a coverData image file, and have that loaded as coverData, give a file name by which to save the stegData, ask to encode the plainInfo into the coverData and produce stegData.

    Encode requirements
  1. Give a filename to a ClearInfo text file.
  2. Load a ClearInfo text file.
  3. Enter ClearInfo directly.
  4. Give a filename to a CoverData image file.
  5. Load a CoverData image file.
  6. Give a filename for the StegData to be stored.
  7. Encode ClearInfo to generate StegData.
  8. Display stegData.
  9. Display coverData.
  10. Save stegData to a file.

Steganography Use Case Encode

When decoding the user will be able to specify a stegData file name to load to decode, give a file name for the resulting plainInfo to be saved in.

    Decode requirements
  1. Give a filename to a StegData image file.
  2. Load a StegData image file.
  3. Give a filename for a plainInfo file.
  4. Decode stegData into plainInfo.
  5. Display plainInfo.
  6. Save plainInfo to a file.

Steganography Use Case Decode

Data Structures

The application will model data structures of ClearInfo, ClearInfo filename, PlainInfo, CoverData, CoverData filename, prepared CoverData, StegData, and StegData filename. The CoverData to StegData line will be image based, but the encode and decode method will be kept separate to give some flexibility to adapt to other forms of coverData. The ClearInfo will be text, but yet again the plainInfo will be treated just as standard data.

    Data structures
  1. ClearInfo
  2. ClearInfo filename
  3. PlainInfo
  4. CoverData
  5. CoverData filename
  6. Prepared CoverData
  7. StegData
  8. StegData filename

Both encode and decode could use the same structure, but to allow a higer degree of flexibility in the design better to make each structure a different object for each process, so the data structures are kept in separate classes to facilitate this. The CoverData though will not be used in the decode. We can get back to the prepared CoverData, but there is no way to deduce what the original CoverData would have been. -ed current encoding technique does allow for this though

Data Flows

Steganography Encoding DataFlow

Steganography Decoding DataFlow

Code

We have enough analysis done now to crack out some code. So fire up your favourite editor, I will be using vim, but feel free to use what suits you best.

I am going to keep things fairly modular, so I will begin with the standard python setup followed by the basic class framework to be used.

Let's call the module steg.py and store it in a new directory called steg.

Command Line

> cd
> mkdir steg
> cd steg
> vi steg.py

The standard python setup.

#!/usr/bin/python
#-----------------------------------------------------------
#     _
#  __| |_ ___ __ _   _ __ _  _
# (_-<  _/ -_) _` |_| '_ \ || |
# /__/\__\___\__, (_) .__/\_, |
#            |___/  |_|   |__/
#-----------------------------------------------------------
# File      : ~/steg/steg.py
# Author    : Poised Solutions
# Project   : Steganography
# Syntax    : python
# Copyright : Poised Solutions Ltd
#-----------------------------------------------------------
# Basic Steganography library
#-----------------------------------------------------------

"""Basic Steganography library designed to act as a source
code walkthrough demonstration of how to build a framework
for a Steganography application."""
#-----------------------------------------------------------

def main():
    """Test steg module."""
    pass
#-----------------------------------------------------------

if __name__ == "__main__":
    main()
#-----------------------------------------------------------

Test functions added, and major classes identified:

...
#-----------------------------------------------------------

class DataStruct(object):
    """Data structure class."""

    def __init__(self):
        """Initialize data structure object."""
        pass
#-----------------------------------------------------------

class Encode(DataStruct):
    """Encode class."""

    def __init__(self):
        """Initialize encode object."""
        pass
#-----------------------------------------------------------

class Decode(DataStruct):
    """Decode class."""

    def __init__(self):
        """Initialize decode object."""
        pass
#-----------------------------------------------------------

def testEncode():
    """Test encoding."""

    print "Testing encode."
#-----------------------------------------------------------

def testDecode():
    """Test decoding."""

    print "Testing decode."
#-----------------------------------------------------------

def main():
    """Test steg module."""

    print "Testing steg module."

    testEncode()

    testDecode()
#-----------------------------------------------------------

...

Now some developer magic, a Helper class has been added, the plainInfo should be generic, so a text to plainInfo generator is used. The coverData is going to be an image but again I want to be generic, so the image will be made to iterate through the channel values.

The classes have all been fleshed out, with most of the functionality expressed if not defined completely.

The python imaging library has also been imported.

The basic structure for the module is in place, just the blanks to fill in.

...

import Image
#-----------------------------------------------------------

class DataStruct(object):
    """Data structure class."""

    def __init__(self):
        """Initialize data structure object."""
        pass
    #------------------------------------------------------

    def setClearInfo(self, clearInfo):
        """Set clearInfo."""
        pass
    #------------------------------------------------------

    def getClearInfo(self):
        """Get clearInfo."""
        pass
    #------------------------------------------------------

    def setClearInfoFileName(self, clearInfoFN):
        """Set clearInfo file name."""
        pass
    #------------------------------------------------------

    clearInfoFileName = property(
        lambda self: self._clearInfoFileName,
        setClearInfoFileName,
        None,
        "ClearInfo file name.")
    #------------------------------------------------------

    def loadClearInfoTextFile(self):
        """Load ClearInfo from text file."""
        pass
    #------------------------------------------------------

    clearInfo = property(
        getClearInfo,
        setClearInfo,
        None,
        "ClearInfo.")
    #------------------------------------------------------

    def setPlainInfo(self, plainInfo):
        """Set plainInfo."""
        pass
    #------------------------------------------------------

    plainInfo = property(
        lambda self: self._plainInfo,
        setPlainInfo,
        None,
        "PlainInfo.")
    #------------------------------------------------------

    def setCoverData(self, coverData):
        """Set coverData."""
        pass
    #------------------------------------------------------

    coverData = property(
        lambda self: self._coverData,
        setCoverData,
        None,
        "CoverData.")
    #------------------------------------------------------

    def setCoverDataFileName(self, coverDataFN):
        """Set coverData file name."""
        pass
    #------------------------------------------------------

    coverDataFileName = property(
        lambda self: self._coverDataFileName,
        setCoverDataFileName,
        None,
        "CoverData file name.")
    #------------------------------------------------------

    def loadCoverDataImgFile(self):
        """Load coverData from image file."""
        pass
    #------------------------------------------------------

    def setStegDataFileName(self, stegDataFN):
        """Set StegData FileName."""
        pass
    #------------------------------------------------------

    def setStegData(self, stegData):
        """Set StegData."""
        pass
    #------------------------------------------------------

    stegData = property(
        lambda self: self._stegData,
        setStegData,
        None,
        "StegData.")
    #------------------------------------------------------

    stegDataFileName = property(
        lambda self: self._stegDataFileName,
        setStegDataFileName,
        None,
        "StegData file name.")
    #------------------------------------------------------

    def loadStegDataImgFile(self):
        """Load stegData image file."""
        pass
    #------------------------------------------------------

    def saveStegDataImgFile(self):
        """Save stegData image file."""
        pass
#-----------------------------------------------------------

class Encode(DataStruct):
    """Encode class."""

    def __init__(self):
        """Initialize encode object."""
        pass
    #------------------------------------------------------

    def encode(self):
        """Encode PlainInfo into Prepared CoverData."""
        pass
    #------------------------------------------------------

    def checkReady(self):
        """Check ready to encode."""

        # plainInfo given?

        # coverData given?

        # coverData large enough?

        return True
    #------------------------------------------------------

    def prepareCoverData(self):
        """Prepare CoverData."""
        pass
    #------------------------------------------------------

    def checkCoverData(self):
        """Check CoverData for adequate size."""
        pass
#-----------------------------------------------------------

class Decode(DataStruct):
    """Decode class."""

    def __init__(self):
        """Initialize decode object."""
        pass
    #------------------------------------------------------

    def decode(self):
        """Decode PlainInfo from StegData."""
        pass
    #------------------------------------------------------

    def checkReady(self):
        """Check ready to decode."""

        # stegData given?

        return True
#-----------------------------------------------------------

class Helper(object):
    """Steganography helper class."""

    @staticmethod
    def clearTxt2PlainInfo(txt):
        """Clear Text to PlainInfo."""
        pass
    #------------------------------------------------------

    @staticmethod
    def plainInfo2ClearTxt(plainInfo):
        """PlainInfo to Clear Text."""
        pass
    #------------------------------------------------------

    @staticmethod
    def img2CoverData(img):
        """Image to CoverData."""
        pass
#-----------------------------------------------------------

def testEncode():
    """Test encoding."""

    print "Testing Encode class."

    encode = Encode()

    encode.clearInfo = \
"""Through sheer abandum
fly the messages
through the knowing
into the zip
a deep well
Obscured objects, onomatophobia
an aged amphidiscophoran
down deep dove
Does buck does
I i i
the import subject
to re reproduce"""
    # Poised Solutions bears no responsibilty for the
    # quality of the above poetry - ed

    # philistine - poet

    encode.coverDataFileName = \
        "coverTest.png"

    encode.encode()

    encode.stegDataFileName = \
        "stegTest.png"

    encode.saveStegDataImgFile()
#-----------------------------------------------------------

def testDecode():
    """Test decoding."""

    print "Testing Decode Class."

    decode = Decode()

    decode.stegDataFile = \
        "stegTest.png"

    decode.decode()
#-----------------------------------------------------------
...

DataStruct class is completed and the testing for it added. ImageDraw module added.

...

import ImageDraw
#-----------------------------------------------------------

class DataStruct(object):
    """Data structure class."""

    def __init__(self):
        """Initialize data structure object."""
        pass
    #------------------------------------------------------

    def setClearInfo(self, clearInfo = None):
        """Set clearInfo."""

        if clearInfo:
            self._clearInfo = clearInfo
            print "clearInfo set to '%s'." % (
                self.clearInfo)
        else:
            print "Problem setting clearInfo."
    #------------------------------------------------------

    def getClearInfo(self):
        """Get clearInfo."""

        return self._clearInfo
    #------------------------------------------------------

    clearInfo = property(
        getClearInfo,
        setClearInfo,
        None,
        "ClearInfo.")
    #------------------------------------------------------

    def setClearInfoFileName(self, clearInfoFN = None):
        """Set clearInfo file name."""

        if clearInfoFN:
            self._clearInfoFileName = clearInfoFN
            print "clearInfoFileName set to '%s'." % (
                self.clearInfoFileName)
        else:
            print "Problem setting clearInfoFileName."
    #------------------------------------------------------

    clearInfoFileName = property(
        lambda self: self._clearInfoFileName,
        setClearInfoFileName,
        None,
        "ClearInfo file name.")
    #------------------------------------------------------

    def loadClearInfoTextFile(self, clearInfoFN = None):
        """Load ClearInfo from text file."""

        if clearInfoFN:
            self.clearInfoFileName = clearInfoFN

        if not hasattr(self, 'clearInfoFileName'):
            print "Problem clearInfoFileName not set."
            return

        if self.clearInfoFileName == "":
            print \
                "Problem clearInfoFileName set to " + \
                "empty string."
            return

        try:
            fh  = open(self.clearInfoFileName, 'r')
            txt = fh.read()
        except:
            print \
            "Problem loading clearInfo file '%s'." % (
                self.clearInfoFileName)
            return

        print "clearInfo file loaded '%s'." % (
            self.clearInfoFileName)

        self.clearInfo = txt
    #-----------------------------------------------------

    def setPlainInfo(self, plainInfo = None):
        """Set plainInfo."""

        if not plainInfo:
            print "Problem setting plainInfo."
            return

        if not isinstance(plainInfo, list):
            print "Problem setting plainInfo " + \
                "(list type required)."
            return

        self._plainInfo = plainInfo
        print "plainInfo set to '%s'." % (
            self.plainInfo)
    #------------------------------------------------------

    plainInfo = property(
        lambda self: self._plainInfo,
        setPlainInfo,
        None,
        "PlainInfo.")
    #------------------------------------------------------

    def setCoverData(self, coverData = None):
        """Set coverData."""

        if not coverData:
            print "Problem setting coverData."
            return

        if not isinstance(coverData, Image.Image):
            print "Problem setting coverData " + \
                "(Image.Image type required)"
            return

        self._coverData = coverData
        print "coverData set to image being shown"
        self.coverData.show()
    #------------------------------------------------------

    coverData = property(
        lambda self: self._coverData,
        setCoverData,
        None,
        "CoverData.")
    #------------------------------------------------------

    def setCoverDataFileName(self, coverDataFN = None):
        """Set coverData file name."""

        if not coverDataFN:
            print "Problem setting coverData file name."
            return

        self._coverDataFileName = coverDataFN
        print "coverDataFileName set to '%s'." % (
            self.coverDataFileName)
    #------------------------------------------------------

    coverDataFileName = property(
        lambda self: self._coverDataFileName,
        setCoverDataFileName,
        None,
        "CoverData file name.")
    #------------------------------------------------------

    def loadCoverDataImgFile(self, coverDataFN = None):
        """Load coverData from image file."""

        if coverDataFN:
            self.coverDataFileName = coverDataFN

        if not hasattr(self, 'coverDataFileName'):
            print "Problem coverDataFileName not set."
            return

        if self.coverDataFileName == "":
            print \
            "Problem coverDataFileName set to empty string."
            return

        try:
            img  = Image.open(self.coverDataFileName)
        except:
            print \
                "Problem loading coverData image file " + \
                "'%s'." % (
                self.coverDataFileName)
            return

        print "coverData file loaded."
        self.coverData = img
    #------------------------------------------------------

    def setStegData(self, stegData = None):
        """Set StegData."""

        if not stegData:
            print "Problem setting stegData."
            return

        if not isinstance(stegData, Image.Image):
            print "Problem setting stegData " + \
                "(Image.Image type required)"
            return

        self._stegData = stegData
        print "stegData set."
        self.stegData.show()
    #------------------------------------------------------

    stegData = property(
        lambda self: self._stegData,
        setStegData,
        None,
        "StegData.")
    #------------------------------------------------------

    def setStegDataFileName(self, stegDataFN = None):
        """Set StegData FileName."""

        if not stegDataFN:
            print "Problem setting stegData file name."
            return

        self._stegDataFileName =stegDataFN
        print "stegDataFileName set to '%s'." % (
            self.stegDataFileName)
    #------------------------------------------------------

    stegDataFileName = property(
        lambda self: self._stegDataFileName,
        setStegDataFileName,
        None,
        "StegData file name.")
    #------------------------------------------------------

    def loadStegDataImgFile(self, stegDataFN = None):
        """Load stegData image file."""

        if stegDataFN:
            self.stegDataFileName = stegDataFN

        if not hasattr(self, 'stegDataFileName'):
            print "Problem stegDataFileName not set."
            return

        if self.stegDataFileName == "":
            print \
            "Problem stegDataFileName set to empty string."
            return

        try:
            img  = Image.open(self.stegDataFileName)
        except:
            print \
            "Problem loading stegData image file '%s'." % (
                self.stegDataFileName)
            return

        print "stegData file loaded."
        self.stegData = img
    #------------------------------------------------------

    def saveStegDataImgFile(self, stegDataFN = None):
        """Save stegData image file."""

        if stegDataFN:
            self.stegDataFileName = stegDataFN

        if not hasttr(self, 'stegDataFileName'):
            print "Problem stegDataFileName not set."
            return

        if self.stegDataFileName == "":
            print \
            "Problem stegDataFileName set to empty string."
            return

        if not hasattr(self, 'stegData'):
            print "Problem no stegData to save."
            return

        if not isinstance(self.stegData, Image.Image):
            print \
                "Problem stegData is not of " + \
                "type Image.Image"
            return

        try:
            self.stegData.save(self.stegDataFileName)
        except:
            print \
            "Problem saving stegData image file '%s'." % (
                self.stegDataFileName)
            return

        print "stegData file saved."
#-----------------------------------------------------------

...


def testDataStruct():
    """Test DataStruct class."""

    print "Testing DataStruct class."

    ds = DataStruct()

    ds.clearInfo = None

    ds.clearInfo = "Hello Steganography."

    ds.clearInfoFileName = \
        "clearinfo.txt"

    ds.loadClearInfoTextFile()

    ds.plainInfo = "Fail"

    ds.plainInfo = ['a','b','c']

    ds.coverData = [1.999, 1.999, 1.999]

    ds.coverData = Image.new('RGB', (256, 256), "#007700")

    ds.coverData.save('coverdata.png')

    ds.coverData = Image.new('RGB', (128, 128), "#007007")

    ds.coverDataFileName = \
        "coverdata.png"

    ds.loadCoverDataImgFile()

    ds.stegData = [128, 128, 128]

    ds.stegData = Image.new('RGB', (256, 256), "#700700")

    draw = ImageDraw.Draw(ds.stegData)
    draw.line((0, 0,
        ds.stegData.size[0], ds.stegData.size[1]),
        fill = 255)

    ds.stegDataFileName = \
        "stegdata.png"

    ds.saveStegDataImgFile()

    ds.loadStegDataImgFile()
#-----------------------------------------------------------

def main():
    """Test steg module."""

    print "Testing steg module."

    testDataStruct()

    #testEncode()

    #testDecode()
#-----------------------------------------------------------

...

Helper class is completed and testing for it added.

Image generator class added and the idea extended to include StegData images.

PlainInfo bit generator class (note bits are backwards).

...

class Helper(object):
    """Steganography helper class."""

    @staticmethod
    def clearTxt2PlainInfo(txt):
        """Clear Text to PlainInfo."""

        # string to list
        plainInfo = list(txt)

        return plainInfo
    #------------------------------------------------------

    @staticmethod
    def plainInfo2ClearTxt(plainInfo):
        """PlainInfo to Clear Text."""

        #list to string
        clearTxt = "".join(plainInfo)

        return clearTxt
    #------------------------------------------------------

    @staticmethod
    def plainInfo2bitInfo(plainInfo):
        """PlainInfo to BitInfo."""

        bitInfo = BitGen(plainInfo)

        return bitInfo
    #------------------------------------------------------

    @staticmethod
    def img2CoverData(img):
        """Image to CoverData."""

        coverDataGen = ImgGen(img)

        return coverDataGen
    #------------------------------------------------------

    @staticmethod
    def img2StegData(img):
        """Image to StegData."""

        stegDataGen = ImgGen(img)

        return stegDataGen
#-----------------------------------------------------------

class ImgGen:
    """Image Generator Class."""

    def __init__(self, img):
        """Initialize image generating object."""

        self.img = img
        self.reset()

    def next(self):
        """Return next channel value."""

        pixelPos = (self.pixel[0], self.pixel[1])

        channel = self.channel

        try:
            value = \
                self.img.getpixel(pixelPos
                    )[channel]
        except:
            raise "Problem getting next pixel."

        self.channel += 1

        if self.channel >= 3:
            self.channel = 0
            self.pixel[0] += 1

        if self.pixel[0] >= self.img.size[0]:
            self.pixel[0]  = 0
            self.pixel[1] += 1

        if self.pixel[1] >= self.img.size[1]:
            return (-1, (-1, -1), -1)

        return (value, (pixelPos), channel)

    def reset(self):
        """Reset ImgGen."""

        self.pixel   = [0, 0]
        self.channel = 0
        self.value   = 0
#-----------------------------------------------------------

class BitGen:
    """Bit Generator Class."""

    def __init__(self, l):
        """Initialize bit generating object."""

        self.l = l

    def bit(self):
        """Yield next bit value."""

        lpos   = 0
        bpos   = 0
        binary = ord(self.l[lpos])

        while 1:
            yield binary & 1

            binary >>= 1

            bpos += 1
            if bpos == 8:
                bpos   = 0
                lpos  += 1
                if lpos == len(self.l):
                    return
                binary = ord(self.l[lpos])
#-----------------------------------------------------------

...

def testHelper():
    """Test Helper class."""

    print "Testing Helper class."

    clearTxt = "Hello there"
    print clearTxt

    plainInfo = Helper.clearTxt2PlainInfo(clearTxt)
    print plainInfo

    clearTxt = Helper.plainInfo2ClearTxt(plainInfo)
    print clearTxt

    img = Image.new('RGB', (16, 16), "#7000a0")

    draw = ImageDraw.Draw(img)
    draw.line((0, 0,
        img.size[0], img.size[1]),
        fill = "#ffffff")
    draw.line((0, img.size[1],
        img.size[0], 0),
        fill = "#000000")

    coverData = Helper.img2CoverData(img)

    print "CoverData Image Channel Values:"
    k = 0

    cValue = coverData.next()
    while cValue[0] != -1:
        print "%s" % (cValue[0]),

        k += 1
        if k == img.size[0] * 3:
            k = 0
            print
        cValue = coverData.next()

    bitInfo = Helper.plainInfo2bitInfo(plainInfo)

    k = 0
    for bit in bitInfo.bit():
        print "%s" % (bit),

        k += 1
        if k == 8:
            k = 0
            print
#-----------------------------------------------------------

def main():
    """Test steg module."""

    print "Testing steg module."

    #testDataStruct()

    testHelper()

    #testEncode()

    #testDecode()
#-----------------------------------------------------------

...

Encoding and decoding completed, testing for them added.


class Encode(DataStruct):
    """Encode class."""

    def __init__(self):
        """Initialize encode object."""

        self.key  = "z"
    #------------------------------------------------------

    def encode(self):
        """Encode PlainInfo into Prepared CoverData."""

        if not self.checkReady():
            print "Problem encoding."
            return

        self.prepareBitInfo()

        if not self.prepareCoverData():
            return False;

        self.stegData = self.coverData.copy()

        coverData = Helper.img2CoverData(self.coverData)

        for bit in self.bitInfo.bit():

            value = coverData.next()

            color = list(
                self.stegData.getpixel(value[1])[:3])

            color[value[2]] = value[0] + bit

            self.stegData.putpixel(value[1], tuple(color))
    #------------------------------------------------------

    def checkReady(self):
        """Check ready to encode."""

        if hasattr(self, 'clearInfo'):
            self.plainInfo = \
                Helper.clearTxt2PlainInfo(
                    self.clearInfo)

        if not hasattr(self, 'plainInfo'):
            print "Problem no plainInfo for encoding."
            return False

        if not hasattr(self, 'coverData'):
            print "Problem no coverData for encoding."
            return False

        if not self.checkCoverData():
            return False

        return True
    #------------------------------------------------------

    def prepareBitInfo(self):
        """Prepare BitInfo."""

        # find the occurance of the first number
        firstNum = len(self.plainInfo)
        length   = str(firstNum)
        i        = -1
        for c in self.plainInfo:
            if ord('0') <= ord(c) <= ord('9'):
                # 0 - 9 inclusive
                firstNum = i
                break
            i += 1

        if firstNum < 0:
            firstNum = 0

        pos = random.randint(0, firstNum)

        length += chr(random.randint(65, 112))

        self.plainInfo = \
            self.plainInfo[0:pos] + \
            list(length) + \
            self.plainInfo[pos:]

        self.obscurePlainInfo()

        self.bitInfo = \
            Helper.plainInfo2bitInfo(self.plainInfo)
    #------------------------------------------------------

    def obscurePlainInfo(self):
        """Obscure PlainInfo."""

        # simple cyclic xor obscuring
        # hides the numeric length
        i   = 0
        key = self.key
        for c in self.plainInfo:
            self.plainInfo[i] = chr(ord(c) ^ ord(key))
            key = chr(
                ord(c) ^ (ord(self.plainInfo[i]) >> 1))

            i  += 1
        print "obscured plainInfo ", self.plainInfo
    #------------------------------------------------------

    def prepareCoverData(self):
        """Prepare CoverData."""

        if not hasattr(self, "coverData"):
            print "Problem no coverData supplied."
            return False

        coverData = Helper.img2CoverData(
            self.coverData)

        maxLen = len(self.plainInfo) * 8
        for k in xrange(0, maxLen):
            # baseline is even
            value = coverData.next()

            color = list(
                self.coverData.getpixel(value[1])[:3])

            color[value[2]] = value[0] - (value[0] & 1)

            self.coverData.putpixel(value[1], tuple(color))

        return True
    #------------------------------------------------------

    def checkCoverData(self):
        """Check CoverData for adequate size."""

        # plainInfo size
        piSize = len(self.plainInfo) * 8

        # length of message length
        msgLenLen = (len(str(piSize)) +1) * 8
        piSize   += msgLenLen

        cdSize = \
            self.coverData.size[0] * \
            self.coverData.size[1] * 3

        if piSize > cdSize:
            print "Problem coverData not large enough."
            return False

        return True
#-----------------------------------------------------------

class Decode(DataStruct):
    """Decode class."""

    def __init__(self):
        """Initialize decode object."""

        self.key = "z"
    #------------------------------------------------------

    def decode(self):
        """Decode PlainInfo from StegData."""

        if not self.checkReady():
            print "Problem decoding."
            return

        stegData = Helper.img2StegData(self.stegData)

        c   = 0
        key = self.key
        foundLen  = False
        plainInfo = []
        binStr    = ""
        i         = 0
        msgLen    = -1
        msgLenStr = ""
        inLen     = False

        value = stegData.next()
        while value[0] != -1:

            # encoded backwards
            binStr = str(value[0] & 1) + binStr

            c += 1

            if c == 8:
                c = 0

                # character in binStr
                #print binStr
                ch     = chr(int(binStr, 2))
                binStr = ""

                # descureCharacter
                chD = chr(ord(ch) ^ ord(key))
                key = chr(ord(chD) ^ (ord(ch) >> 1))

                if foundLen:
                    i += 1
                    plainInfo += chD
                elif ord('0') <= ord(chD) <= ord('9'):
                    msgLenStr += chD
                    inLen = True
                elif inLen:
                    foundLen = True
                    msgLen   = int(msgLenStr)
                    inLen    = False
                else:
                    i += 1
                    plainInfo += chD

            if foundLen:
                if i >= msgLen:
                    break

            value = stegData.next()

        self.plainInfo = plainInfo

        self.clearInfo = Helper.plainInfo2ClearTxt(
            self.plainInfo)
    #------------------------------------------------------

    def checkReady(self):
        """Check ready to decode."""

        if not hasattr(self, 'stegData'):
            print "Problem decoding no stegData"
            return False

        return True
#-----------------------------------------------------------

Full sourcecode listing, zlib compression added, plainInfo list cut, default testing moved to a new function.

#!/usr/bin/python
#-----------------------------------------------------------
#     _
#  __| |_ ___ __ _   _ __ _  _
# (_-<  _/ -_) _` |_| '_ \ || |
# /__/\__\___\__, (_) .__/\_, |
#            |___/  |_|   |__/
#-----------------------------------------------------------
# File      : ~/steg/steg.py
# Author    : Poised Solutions
# Project   : Steganography
# Syntax    : python
# Copyright : Poised Solutions Ltd
#-----------------------------------------------------------
# Basic Stenganography Library
#-----------------------------------------------------------

"""Basic Steganography library designed to act as a source
code walkthrough demonstration of how to build a framework
for a Steganography application."""
#-----------------------------------------------------------

import Image
import ImageDraw
import random
import zlib
#-----------------------------------------------------------

class DataStruct(object):
    """Data structure class."""

    def __init__(self):
        """Initialize data structure object."""
        pass
    #------------------------------------------------------

    def setClearInfo(self, clearInfo = None):
        """Set clearInfo."""

        if clearInfo:
            self._clearInfo = clearInfo
            print "clearInfo set to '%s'." % (
                self.clearInfo)
        else:
            print "Problem setting clearInfo."
    #------------------------------------------------------

    def getClearInfo(self):
        """Get clearInfo."""

        return self._clearInfo
    #------------------------------------------------------

    clearInfo = property(
        getClearInfo,
        setClearInfo,
        None,
        "ClearInfo.")
    #------------------------------------------------------

    def setClearInfoFileName(self, clearInfoFN = None):
        """Set clearInfo file name."""

        if clearInfoFN:
            self._clearInfoFileName = clearInfoFN
            print "clearInfoFileName set to '%s'." % (
                self.clearInfoFileName)
        else:
            print "Problem setting clearInfoFileName."
    #------------------------------------------------------

    clearInfoFileName = property(
        lambda self: self._clearInfoFileName,
        setClearInfoFileName,
        None,
        "ClearInfo file name.")
    #------------------------------------------------------

    def loadClearInfoTextFile(self, clearInfoFN = None):
        """Load ClearInfo from text file."""

        if clearInfoFN:
            self.clearInfoFileName = clearInfoFN

        if not hasattr(self, 'clearInfoFileName'):
            print "Problem clearInfoFileName not set."
            return

        if self.clearInfoFileName == "":
            print \
                "Problem clearInfoFileName set to " + \
                "empty string."
            return

        try:
            fh  = open(self.clearInfoFileName, 'r')
            txt = fh.read()
        except:
            print \
            "Problem loading clearInfo file '%s'." % (
                self.clearInfoFileName)
            return

        print "clearInfo file loaded '%s'." % (
            self.clearInfoFileName)

        self.clearInfo = txt
    #-----------------------------------------------------

    def setPlainInfo(self, plainInfo = None):
        """Set plainInfo."""

        if not plainInfo:
            print "Problem setting plainInfo."
            return

        if not isinstance(plainInfo, list):
            print "Problem setting plainInfo " + \
                "(list type required)."
            return

        self._plainInfo = plainInfo
        print "plainInfo set to '%s'." % (
            self.plainInfo)
    #------------------------------------------------------

    plainInfo = property(
        lambda self: self._plainInfo,
        setPlainInfo,
        None,
        "PlainInfo.")
    #------------------------------------------------------

    def setCoverData(self, coverData = None):
        """Set coverData."""

        if not coverData:
            print "Problem setting coverData."
            return

        if not isinstance(coverData, Image.Image):
            print "Problem setting coverData " + \
                "(Image.Image type required)"
            return

        self._coverData = coverData
        print "coverData set to image being shown"
        self.coverData.show()
    #------------------------------------------------------

    coverData = property(
        lambda self: self._coverData,
        setCoverData,
        None,
        "CoverData.")
    #------------------------------------------------------

    def setCoverDataFileName(self, coverDataFN = None):
        """Set coverData file name."""

        if not coverDataFN:
            print "Problem setting coverData file name."
            return

        self._coverDataFileName = coverDataFN
        print "coverDataFileName set to '%s'." % (
            self.coverDataFileName)
    #------------------------------------------------------

    coverDataFileName = property(
        lambda self: self._coverDataFileName,
        setCoverDataFileName,
        None,
        "CoverData file name.")
    #------------------------------------------------------

    def loadCoverDataImgFile(self, coverDataFN = None):
        """Load coverData from image file."""

        if coverDataFN:
            self.coverDataFileName = coverDataFN

        if not hasattr(self, 'coverDataFileName'):
            print "Problem coverDataFileName not set."
            return

        if self.coverDataFileName == "":
            print \
            "Problem coverDataFileName set to empty string."
            return

        try:
            img  = Image.open(self.coverDataFileName)
        except:
            print \
                "Problem loading coverData image file " + \
                "'%s'." % (
                self.coverDataFileName)
            return

        print "coverData file loaded."
        self.coverData = img
    #------------------------------------------------------

    def setStegData(self, stegData = None):
        """Set StegData."""

        if not stegData:
            print "Problem setting stegData."
            return

        if not isinstance(stegData, Image.Image):
            print "Problem setting stegData " + \
                "(Image.Image type required)"
            return

        self._stegData = stegData
        print "stegData set."
        self.stegData.show()
    #------------------------------------------------------

    stegData = property(
        lambda self: self._stegData,
        setStegData,
        None,
        "StegData.")
    #------------------------------------------------------

    def setStegDataFileName(self, stegDataFN = None):
        """Set StegData FileName."""

        if not stegDataFN:
            print "Problem setting stegData file name."
            return

        self._stegDataFileName =stegDataFN
        print "stegDataFileName set to '%s'." % (
            self.stegDataFileName)
    #------------------------------------------------------

    stegDataFileName = property(
        lambda self: self._stegDataFileName,
        setStegDataFileName,
        None,
        "StegData file name.")
    #------------------------------------------------------

    def loadStegDataImgFile(self, stegDataFN = None):
        """Load stegData image file."""

        if stegDataFN:
            self.stegDataFileName = stegDataFN

        if not hasattr(self, 'stegDataFileName'):
            print "Problem stegDataFileName not set."
            return

        if self.stegDataFileName == "":
            print \
            "Problem stegDataFileName set to empty string."
            return

        try:
            img  = Image.open(self.stegDataFileName)
        except:
            print \
            "Problem loading stegData image file '%s'." % (
                self.stegDataFileName)
            return

        print "stegData file loaded."
        self.stegData = img
    #------------------------------------------------------

    def saveStegDataImgFile(self, stegDataFN = None):
        """Save stegData image file."""

        if stegDataFN:
            self.stegDataFileName = stegDataFN

        if not hasattr(self, 'stegDataFileName'):
            print "Problem stegDataFileName not set."
            return

        if self.stegDataFileName == "":
            print \
            "Problem stegDataFileName set to empty string."
            return

        if not hasattr(self, 'stegData'):
            print "Problem no stegData to save."
            return

        if not isinstance(self.stegData, Image.Image):
            print \
                "Problem stegData is not of " + \
                "type Image.Image"
            return

        try:
            self.stegData.save(self.stegDataFileName)
        except:
            print \
            "Problem saving stegData image file '%s'." % (
                self.stegDataFileName)
            return

        print "stegData file saved."
#-----------------------------------------------------------

class Encode(DataStruct):
    """Encode class."""

    def __init__(self):
        """Initialize encode object."""

        self.key  = "z"
    #------------------------------------------------------

    def encode(self):
        """Encode PlainInfo into Prepared CoverData."""

        if not self.checkReady():
            print "Problem encoding."
            return

        self.prepareBitInfo()

        if not self.prepareCoverData():
            return False;

        self.stegData = self.coverData.copy()

        coverData = Helper.img2CoverData(self.coverData)

        for bit in self.bitInfo.bit():

            value = coverData.next()

            color = list(
                self.stegData.getpixel(value[1])[:3])

            color[value[2]] = value[0] + bit

            self.stegData.putpixel(value[1], tuple(color))
    #------------------------------------------------------

    def checkReady(self):
        """Check ready to encode."""

        if hasattr(self, 'clearInfo'):
            self.plainInfo = \
                Helper.clearTxt2PlainInfo(
                    self.clearInfo)

        if not hasattr(self, 'plainInfo'):
            print "Problem no plainInfo for encoding."
            return False

        if not hasattr(self, 'coverData'):
            print "Problem no coverData for encoding."
            return False

        if not self.checkCoverData():
            return False

        return True
    #------------------------------------------------------

    def prepareBitInfo(self):
        """Prepare BitInfo."""

        # find the occurance of the first number
        firstNum = len(self.plainInfo)
        length   = str(firstNum)
        i        = -1
        for c in self.plainInfo:
            if ord('0') <= ord(c) <= ord('9'):
                # 0 - 9 inclusive
                firstNum = i
                break
            i += 1

        if firstNum < 0:
            firstNum = 0

        pos = random.randint(0, firstNum)

        length += chr(random.randint(65, 112))

        self.plainInfo = \
            self.plainInfo[0:pos] + \
            list(length) + \
            self.plainInfo[pos:]

        self.obscurePlainInfo()

        self.bitInfo = \
            Helper.plainInfo2bitInfo(self.plainInfo)
    #------------------------------------------------------

    def obscurePlainInfo(self):
        """Obscure PlainInfo."""

        # simple cyclic xor obscuring
        # hides the numeric length
        i   = 0
        key = self.key
        for c in self.plainInfo:
            self.plainInfo[i] = chr(ord(c) ^ ord(key))
            key = chr(
                ord(c) ^ (ord(self.plainInfo[i]) >> 1))

            i  += 1
        print "obscured plainInfo ", self.plainInfo
    #------------------------------------------------------

    def prepareCoverData(self):
        """Prepare CoverData."""

        if not hasattr(self, "coverData"):
            print "Problem no coverData supplied."
            return False

        coverData = Helper.img2CoverData(
            self.coverData)

        maxLen = len(self.plainInfo) * 8
        for k in xrange(0, maxLen):
            # baseline is even
            value = coverData.next()

            color = list(
                self.coverData.getpixel(value[1])[:3])

            color[value[2]] = value[0] - (value[0] & 1)

            self.coverData.putpixel(value[1], tuple(color))

        return True
    #------------------------------------------------------

    def checkCoverData(self):
        """Check CoverData for adequate size."""

        # plainInfo size
        piSize = len(self.plainInfo) * 8

        # length of message length
        msgLenLen = (len(str(piSize)) +1) * 8
        piSize   += msgLenLen

        cdSize = \
            self.coverData.size[0] * \
            self.coverData.size[1] * 3

        if piSize > cdSize:
            print "Problem coverData not large enough."
            return False

        return True
#-----------------------------------------------------------

class Decode(DataStruct):
    """Decode class."""

    def __init__(self):
        """Initialize decode object."""

        self.key = "z"
    #------------------------------------------------------

    def decode(self):
        """Decode PlainInfo from StegData."""

        if not self.checkReady():
            print "Problem decoding."
            return

        stegData = Helper.img2StegData(self.stegData)

        c   = 0
        key = self.key
        foundLen  = False
        plainInfo = []
        binStr    = ""
        i         = 0
        msgLen    = -1
        msgLenStr = ""
        inLen     = False

        value = stegData.next()
        while value[0] != -1:

            # encoded backwards
            binStr = str(value[0] & 1) + binStr

            c += 1

            if c == 8:
                c = 0

                # character in binStr
                #print binStr
                ch     = chr(int(binStr, 2))
                binStr = ""

                # descureCharacter
                chD = chr(ord(ch) ^ ord(key))
                key = chr(ord(chD) ^ (ord(ch) >> 1))

                if foundLen:
                    i += 1
                    plainInfo += chD
                elif ord('0') <= ord(chD) <= ord('9'):
                    msgLenStr += chD
                    inLen = True
                elif inLen:
                    foundLen = True
                    msgLen   = int(msgLenStr)
                    inLen    = False
                else:
                    i += 1
                    plainInfo += chD

            if foundLen:
                if i >= msgLen:
                    break

            value = stegData.next()

        self.plainInfo = plainInfo

        self.clearInfo = Helper.plainInfo2ClearTxt(
            self.plainInfo)
    #------------------------------------------------------

    def checkReady(self):
        """Check ready to decode."""

        if not hasattr(self, 'stegData'):
            print "Problem decoding no stegData"
            return False

        return True
#-----------------------------------------------------------

class Helper(object):
    """Steganography helper class."""

    @staticmethod
    def clearTxt2PlainInfo(txt):
        """Clear Text to PlainInfo."""

        # zip up helps to avoid repeats
        # showing in pattern
        txt = zlib.compress(txt)

        # string to list
        plainInfo = list(txt)

        print "Compressed plainInfo " + str(plainInfo)

        # chop list into two and reglue
        # zip will leave an identifier
        # at front
        mid       = len(plainInfo) // 2
        plainInfo = plainInfo[mid:] + plainInfo[:mid]

        return plainInfo
    #------------------------------------------------------

    @staticmethod
    def plainInfo2ClearTxt(plainInfo):
        """PlainInfo to Clear Text."""

        # chop list into two and reglue
        # round up on odd
        mid = len(plainInfo) // 2
        mid += (len(plainInfo) & 1)

        plainInfo = plainInfo[mid:] + plainInfo[:mid]

        print "Compressed plainInfo " + str(plainInfo)

        # list to string
        clearTxt = "".join(plainInfo)

        # decompress
        clearTxt = zlib.decompress(clearTxt)

        return clearTxt
    #------------------------------------------------------

    @staticmethod
    def plainInfo2bitInfo(plainInfo):
        """PlainInfo to BitInfo."""

        bitInfo = BitGen(plainInfo)

        return bitInfo
    #------------------------------------------------------

    @staticmethod
    def img2CoverData(img):
        """Image to CoverData."""

        coverDataGen = ImgGen(img)

        return coverDataGen
    #------------------------------------------------------

    @staticmethod
    def img2StegData(img):
        """Image to StegData."""

        stegDataGen = ImgGen(img)

        return stegDataGen
#-----------------------------------------------------------

class ImgGen:
    """Image Generator Class."""

    def __init__(self, img):
        """Initialize image generating object."""

        self.img = img
        self.reset()

    def next(self):
        """Return next channel value."""

        pixelPos = (self.pixel[0], self.pixel[1])

        channel = self.channel

        try:
            value = \
                self.img.getpixel(pixelPos
                    )[channel]
        except:
            raise "Problem getting next pixel."

        self.channel += 1

        if self.channel >= 3:
            self.channel = 0
            self.pixel[0] += 1

        if self.pixel[0] >= self.img.size[0]:
            self.pixel[0]  = 0
            self.pixel[1] += 1

        if self.pixel[1] >= self.img.size[1]:
            return (-1, (-1, -1), -1)

        return (value, (pixelPos), channel)

    def reset(self):
        """Reset ImgGen."""

        self.pixel   = [0, 0]
        self.channel = 0
        self.value   = 0
#-----------------------------------------------------------

class BitGen:
    """Bit Generator Class."""

    def __init__(self, l):
        """Initialize bit generating object."""

        self.l = l

    def bit(self):
        """Yield next bit value."""

        lpos   = 0
        bpos   = 0
        binary = ord(self.l[lpos])

        while 1:
            yield binary & 1

            binary >>= 1

            bpos += 1
            if bpos == 8:
                bpos   = 0
                lpos  += 1
                if lpos == len(self.l):
                    return
                binary = ord(self.l[lpos])
#-----------------------------------------------------------

def testEncode():
    """Test encoding."""

    print "Testing Encode class."

    encode = Encode()

    encode.clearInfo = \
"""Through sheer abandum
fly the messages
through the knowing
into the zip
a deep well
Obscured objects, onomatophobia
an aged amphidiscophoran
dove dove down
Does buck does
I i i
the import subject
to re reproduce"""
    # Poised Solutions bears no responsibilty for the
    # quality of the above poetry - ed

    # philistine - poet

    encode.coverDataFileName = \
        "coverTest.png"

    encode.loadCoverDataImgFile()

    encode.encode()

    encode.stegDataFileName = \
        "stegTest.png"

    encode.saveStegDataImgFile()
#-----------------------------------------------------------

def testDecode():
    """Test decoding."""

    print "Testing Decode class."

    decode = Decode()

    decode.stegDataFileName = \
        "stegTest.png"

    decode.loadStegDataImgFile()

    decode.decode()
#-----------------------------------------------------------

def testDataStruct():
    """Test DataStruct class."""

    print "Testing DataStruct class."

    ds = DataStruct()

    ds.clearInfo = None

    ds.clearInfo = "Hello Steganography."

    ds.clearInfoFileName = \
        "clearinfo.txt"

    ds.loadClearInfoTextFile()

    ds.plainInfo = "Fail"

    ds.plainInfo = ['a','b','c']

    ds.coverData = [1.999, 1.999, 1.999]

    ds.coverData = Image.new('RGB', (256, 256), "#007700")

    ds.coverData.save('coverdata.png')

    ds.coverData = Image.new('RGB', (128, 128), "#007007")

    ds.coverDataFileName = \
        "coverdata.png"

    ds.loadCoverDataImgFile()

    ds.stegData = [128, 128, 128]

    ds.stegData = Image.new('RGB', (256, 256), "#700700")

    draw = ImageDraw.Draw(ds.stegData)
    draw.line((0, 0,
        ds.stegData.size[0], ds.stegData.size[1]),
        fill = 255)

    ds.stegDataFileName = \
        "stegdata.png"

    ds.saveStegDataImgFile()

    ds.loadStegDataImgFile()
#-----------------------------------------------------------

def testHelper():
    """Test Helper class."""

    print "Testing Helper class."

    clearTxt = "Hello there"
    print clearTxt

    plainInfo = Helper.clearTxt2PlainInfo(clearTxt)
    print plainInfo

    clearTxt = Helper.plainInfo2ClearTxt(plainInfo)
    print clearTxt

    img = Image.new('RGB', (16, 16), "#7000a0")

    draw = ImageDraw.Draw(img)
    draw.line((0, 0,
        img.size[0], img.size[1]),
        fill = "#ffffff")
    draw.line((0, img.size[1],
        img.size[0], 0),
        fill = "#000000")

    coverData = Helper.img2CoverData(img)

    print "CoverData Image Channel Values:"
    k = 0

    cValue = coverData.next()
    while cValue[0] != -1:
        print "%s" % (cValue[0]),

        k += 1
        if k == img.size[0] * 3:
            k = 0
            print
        cValue = coverData.next()

    bitInfo = Helper.plainInfo2bitInfo(plainInfo)

    k = 0
    for bit in bitInfo.bit():
        print "%s" % (bit),

        k += 1
        if k == 8:
            k = 0
            print
#-----------------------------------------------------------

def defaultTest():
    """Default tests."""

    print "Testing steg module."

    testDataStruct()

    testHelper()

    testEncode()

    testDecode()
#-----------------------------------------------------------

def main():
    """Test steg module."""

    defaultTest()
#-----------------------------------------------------------

if __name__ == "__main__":
    main()
#-----------------------------------------------------------

Conclusion

And that is a basic framework for a Steganography module.

The object oriented design could be altered, perhaps in respect to the Helper class, that could be tied in more with the DataStruct class, but having the Helper separate should allow for code refactoring and to test new ideas out quickly.

Mild encryption is used in the module and embeded in the plainInfo encoding into the coverData, it is data dependant and of course the key can be changed. The length of the message is entered in a random position in the text, followed by a random non numeric character. This tends to further randomize the data to the right of the message length. In essence each resulting stegData image encoded with the same coverData and key will not always be the same, though of course the data derived will be identical.

The encoding itself happens backwards per character. This occurred because of how the bitInfo generator was written, but I liked the idea so kept it in place, it is just more obfuscation.

The obvious drawback to the encoding method used, is the data is at the head of the image and follows a very linear path. This of course is a common Steganography feature but it could be changed easily. The image generator function is the place to do it. The reason I did not want to change this will become clearer later. I wanted this module to be basic with a few clever touches.

Making the pattern a different type and incorporating different patterns is a core concept in Steganography. Using the plainInfo itself to generate the pattern is another smart move. The main reason for varying the pattern is because of Steganography Analysis (Steganalysis the art of cracking Steganography), people look to find original coverData and resulting stegData, once this is achieved it is quite trivial to spot the pattern used. If you always use the same pattern then it can compromise all your stegData previous and in the future. Ways around this is to noise all the stegData, so the message pattern is not obvious even with both the coverData and the stegData of one set.

With all this said, it has to be remembered that Steganography is not encryption, Steganography relies more on stealth, and there is good practice and bad practice in maintaining that stealth. Ensuring the coverData is destroyed after use is a good move, and generating the coverData uniquely is advisable. The coverData itself needs to be non uniform, photographs tend to work well, as does gimping the image a bit for effects, knowledge of how to work basic image transformations across sections is useful, but remember that each time you work on the coverData you could be leaving a trail back to it from which the stegData could be compared.

I introduced a gzip to the clearTxt before converting to plainInfo, this helps in reducing patterns, but does make a pattern of its own, namely the length of the zip itself stored at the front of the data. The message length is going to be added after, so the gzip would not decode with that in place. And, to further hide the fact the message could be a zipped message, the plainInfo is was cut in two and joined back together at opposite ends; this just furthers hides any patterns.

The above example of a Steganography library is not designed for speed. The design instead allows for extension and exploration. Feel free to add code to see the inner working in the various methods, and output the properties to see how they change. If you wish to incorporate any of this code into an application or module just give a cite to Poised Solutions Ltd in your code, and if you have a website a link to this site http://www.poisedsolutions.com/steganography/ or to the Poised Solutions site http://www.poisedsolutions.com would be appreciated.

To download the python source code and the example coverData PNG used in development click here to download.

The code runs through the default tests, but it may not work in every permutation of operation. I have tried to cover most of the bases of how data would flow into the structures to allow data to be injected at certain points, but I may have missed a few possibilities. I will be working with the code to produce a compiled application, so if I spot any glaring errors I will update this page and the downloads.



Home | Introduction | Explanation | Interactive Example
Source Code | Steganalysis | Download | Steg Guess