Click here to Skip to main content
15,881,455 members
Articles / Programming Languages / C#

Steganography 14 - What Text Lists, GIF Images, and HTML Pages have in common

Rate me:
Please Sign up or sign in to vote.
4.83/5 (31 votes)
22 Jan 2005CPOL4 min read 76.6K   2.2K   37  
A simple way to hide binary data in any kind of list
/* This class has been written by
 * Corinna John (Hannover, Germany)
 * cj@binary-universe.net
 * 
 * You may do with this code whatever you like,
 * except selling it or claiming any rights/ownership.
 * 
 * Please send me a little feedback about what you're
 * using this code for and what changes you'd like to
 * see in later versions. (And please excuse my bad english.)
 * 
 * WARNING: This is experimental code.
 * Exception handling has been omitted,
 * to keep the code readable to people who want
 * to understand the algorithm.
 * Please do not expect "Release Quality".
 * */

#region Using directives

using System;
using System.IO;
using System.Data;
using System.Text;
using System.Collections;
using System.Collections.Specialized;

#endregion

namespace SteganoList {
    public class HtmlUtility {
        public HtmlUtility() {}

        /// <summary>Counts the attributes in an HTML document</summary>
        /// <param name="sourceFileName">Path and name of the HTML document</param>
        /// <returns>Count of bytes that can be hidden in the document</returns>
        public int GetCapacity(String sourceFileName) {
            int countCarrierAttributes = 0;

            StreamReader reader = new StreamReader(sourceFileName, Encoding.Default);
            String htmlDocument = reader.ReadToEnd();
            reader.Close();

            HtmlTagCollection tags = FindTags(htmlDocument);

            foreach (HtmlTag tag in tags) {
                countCarrierAttributes += tag.Attributes.Count - 1;
            }

            return countCarrierAttributes;
        }

        /// <summary>Hide a message in an HTML document</summary>
        /// <param name="sourceFileName">Path and name of the HTML document</param>
        /// <param name="destinationFileName">Path and name to save the resulting HTML document</param>
        /// <param name="message">The message to hide</param>
        /// <param name="alphabet">Custom alphabet or empty String</param>
        public void Hide(String sourceFileName, String destinationFileName, Stream message, String alphabet) {
            //read the carrier document
            StreamReader reader = new StreamReader(sourceFileName, Encoding.Default);
            String htmlDocument = reader.ReadToEnd();
            reader.Close();

            message.Position = 0;

            //list the HTML tags
            HtmlTagCollection tags = FindTags(htmlDocument);

            StringBuilder insertTextBuilder = new StringBuilder();
            int offset = 0;
            int messageByte = 0;
            int bitIndex = -1;
            bool messageBit = false;
            int listElementIndex = 0;
            Random random = new Random();

            foreach (HtmlTag tag in tags) {

                insertTextBuilder.Remove(0, insertTextBuilder.Length);
                insertTextBuilder.AppendFormat("<{0}", tag.Name);

                //list attribute names

                String[] attributeNames = new String[tag.Attributes.Count];
                for (int n = 0; n < attributeNames.Length; n++) {
                    attributeNames[n] = tag.Attributes[n].Name;
                }
                
                StringCollection resultList = new StringCollection();
                StringCollection originalList = Utilities.SortLines(attributeNames, alphabet);

                if (tag.Attributes.Count > 1) {

                    //sort attributes

                    for (int n = 0; n < attributeNames.Length - 1; n++) {

                        //get next bit of the message

                        bitIndex++;
                        if (bitIndex == 8) {
                            bitIndex = 0;
                            if (messageByte > -1) {
                                messageByte = message.ReadByte();
                            }
                        }

                        if (messageByte > -1) {

                            //decide which attribute is going to be the next one in the new tag
                            
                            messageBit = ((messageByte & (1 << bitIndex)) > 0) ? true : false;

                            if (messageBit) {
                                listElementIndex = 0;
                            } else {
                                listElementIndex = random.Next(1, originalList.Count);
                            }
                        } else {
                            listElementIndex = 0;
                        }

                        //move the attribute from old list to new list

                        resultList.Add(originalList[listElementIndex]);
                        originalList.RemoveAt(listElementIndex);
                    }
                }

                if (originalList.Count > 0) {
                    //add the last element - it never hides data
                    resultList.Add(originalList[0]);
                }


                HtmlTag.HtmlAttribute attribute;
                foreach (String attributeName in resultList) {
                    attribute = tag.Attributes.GetByName(attributeName);

                    insertTextBuilder.Append(" ");
                    if (attribute.Value.Length > 0) {
                        insertTextBuilder.AppendFormat("{0}={1}", attribute.Name, attribute.Value);
                    } else {
                        insertTextBuilder.Append(attributeName);
                    }
                }

                //replace old tag with new tag

                tag.BeginPosition += offset;
                tag.EndPosition += offset;

                String insertText = insertTextBuilder.ToString();
                int newLength = insertText.Length;
                if (newLength > 0) {
                    int oldLength = tag.EndPosition - tag.BeginPosition;
                    htmlDocument = htmlDocument.Remove(tag.BeginPosition, oldLength);
                    htmlDocument = htmlDocument.Insert(tag.BeginPosition, insertText);

                    offset += (newLength - oldLength);
                }

                if (messageByte < 0) {
                    break; //finished
                }
            }

            //save the new document
            StreamWriter writer = new StreamWriter(destinationFileName);
            writer.Write(htmlDocument);
            writer.Close();
        }

        /// <summary>Extract a hidden message from an HTML document</summary>
        /// <param name="sourceFileName">Path and name of the HTML document</param>
        /// <param name="message">Empty stream for the message</param>
        /// <param name="alphabet">Custom alphabet or empty String</param>
        public Stream Extract(String sourceFileName, String alphabet)
        {
            //initialize empty writer for the message
            BinaryWriter messageWriter = new BinaryWriter(new MemoryStream());

            //read the carrier document
            StreamReader reader = new StreamReader(sourceFileName, Encoding.Default);
            String htmlDocument = reader.ReadToEnd();
            reader.Close();

            //list the HTML tags
            HtmlTagCollection tags = FindTags(htmlDocument);

            int messageLength = 0;
            int messageBit = 0;
            int bitIndex = 0;
            int messageByte = 0;

            foreach (HtmlTag tag in tags) {
             
                if (tag.Attributes.Count > 1) {

                    //list attribute names

                    String[] attributeNames = new String[tag.Attributes.Count];
                    for (int n = 0; n < attributeNames.Length; n++) {
                        attributeNames[n] = tag.Attributes[n].Name;
                    }

                    StringCollection carrierList = new StringCollection();
                    carrierList.AddRange(attributeNames);
                    carrierList.RemoveAt(carrierList.Count - 1);

                    //sort -> get original list
                    StringCollection originalList = Utilities.SortLines(attributeNames, alphabet);
                    String[] unchangeableOriginalList = new String[originalList.Count];
                    originalList.CopyTo(unchangeableOriginalList, 0);

                    foreach (String s in carrierList) {

                        //decide which bit the entry's position hides

                        if (s == originalList[0]) {
                            messageBit = 1;
                        } else {
                            messageBit = 0;
                        }

                        //remove the color from the sorted list
                        originalList.Remove(s);

                        //add the bit to the message
                        messageByte += (byte)(messageBit << bitIndex);

                        bitIndex++;
                        if (bitIndex > 7) {
                            if (messageLength == 0) {
                                messageLength = messageByte;
                            } else {
                                //append the byte to the message
                                messageWriter.Write((byte)messageByte);
                                if (messageWriter.BaseStream.Length == messageLength) {
                                    //finished
                                    break;
                                }
                            }
                            messageByte = 0;
                            bitIndex = 0;
                        }
                    }
                }

                if ((messageLength > 0) && (messageWriter.BaseStream.Length == messageLength)) {
                    //finished
                    break;
                }
            }

            //return message stream
            messageWriter.Seek(0, SeekOrigin.Begin);
            return messageWriter.BaseStream;
        }

        /// <summary>List all HTML tags of a document</summary>
        /// <param name="htmlDocument"></param>
        /// <returns>List with</returns>
        private HtmlTagCollection FindTags(String htmlDocument) {
            HtmlTagCollection tags = new HtmlTagCollection();
            int indexStart = 0, indexEnd = 0;
            String text;
            do {

                indexStart = htmlDocument.IndexOf('<', indexEnd + 1);
                if (indexStart > 0) {
                    indexEnd = htmlDocument.IndexOf('>', indexStart + 1);
                    if (indexEnd > 0) {
                        if (htmlDocument[indexStart + 1] != '/') {
                            //Ende vom Start-Tag gefunden
                            text = htmlDocument.Substring(indexStart, indexEnd - indexStart);
                            tags.Add(new HtmlTag(text, indexStart, indexEnd));
                        }
                    }
                }

            } while (indexStart > 0);

            return tags;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
Corinna lives in Hanover/Germany and works as a C# developer.

Comments and Discussions