/// <summary> /// Hides a <see cref="StegoMessage"/> object inside a /// <see cref="StegoImage"/> object. /// </summary> /// <param name="carrierImage">The carrier image which is to be used to hide the message</param> /// <param name="message">The message which is to be hidden inside the carrier image</param> /// <param name="stegoPassword">The password which is to be used to hide the message (Randomized Hide and Seek)</param> /// <param name="bitPlanes">The amount of bit planes that are to be used to hide the message</param> /// <param name="bitPlanesFirst">true for bit planes first-mode and false for pixels first-mode</param> /// <exception cref="MessageNameTooBigException"></exception> /// <exception cref="MessageTooBigException"></exception> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="FormatException"></exception> /// <exception cref="EncoderFallbackException"></exception> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="System.Reflection.TargetInvocationException"></exception> /// <exception cref="Exception"></exception> /// <returns>The stego image containing the hidden message</returns> public StegoImage HideMessage( StegoImage carrierImage, StegoMessage message, string stegoPassword, int bitPlanes, bool bitPlanesFirst) { // Check if a password is set bool passwordSet = !(stegoPassword.Equals("")); // Generate stego image (Pass by reference) StegoImage stegoImage = new StegoImage( new Bitmap( carrierImage.Image ), carrierImage.Name, carrierImage.SizeInBytes ); // Rename the stego image stegoImage.Name = "stegged_" + Path.GetFileNameWithoutExtension(stegoImage.Name) + ".png"; // Base variable declaration int carrierWidth = carrierImage.Image.Width; int carrierHeight = carrierImage.Image.Height; uint maxCarrierPixels = (uint)(carrierWidth * carrierHeight); uint pixelDistance = 1; // Get the binary string of a message object and its length string completeMessage = GenerateMessageBitPattern(message); uint completeMessageLength = (uint)completeMessage.Length; // Throw exception if the message is too big uint carrierCapacity = CalculateCapacity(carrierImage, bitPlanes); if (completeMessageLength > carrierCapacity * 8) { throw new MessageTooBigException(); } // If a stego password is specified, scramble the image and change the pixelDistance value if (passwordSet) { // Transform the password into a value defining the distance between pixels byte[] passwordBytes = Encoding.UTF8.GetBytes(stegoPassword); passwordBytes = Hasher.HashSha256(passwordBytes); pixelDistance = (uint)((ulong)BitConverter.ToInt64(passwordBytes, 0) % maxCarrierPixels); // Scramble image stegoImage.Image = ImageScrambler.ScrambleOne(stegoImage.Image); stegoImage.Image = ImageScrambler.ScrambleThree(stegoImage.Image); stegoImage.Image = ImageScrambler.ScrambleTwo(stegoImage.Image); } // Hiding variables int messageBitCounter = 0; // Counter iterating over all message bits uint currentPixel = 0; // Variable storing the currently considered pixel uint restClassCounter = 0; // Counter iterating over all rest classes byte bitPlaneSelector = 0; // Variable used to select which bit plane is used for embedding // While there is something left to hide while (messageBitCounter < completeMessageLength) { var currentPixelXValue = (int)(currentPixel % carrierWidth); // x coordinate of current pixel var currentPixelYValue = (int)(currentPixel / carrierWidth); // y coordinate of current pixel // Get current pixel var pixel = stegoImage.Image.GetPixel(currentPixelXValue, currentPixelYValue); // Pixel object used to generate the new color // Define which of the three LSBs of a pixel should be used byte color; switch (messageBitCounter % 3) { case 0: color = SetBit(pixel.R, completeMessage[messageBitCounter].ToString(), bitPlaneSelector); stegoImage.Image.SetPixel(currentPixelXValue, currentPixelYValue, Color.FromArgb(color, pixel.G, pixel.B)); break; case 1: color = SetBit(pixel.G, completeMessage[messageBitCounter].ToString(), bitPlaneSelector); stegoImage.Image.SetPixel(currentPixelXValue, currentPixelYValue, Color.FromArgb(pixel.R, color, pixel.B)); break; case 2: color = SetBit(pixel.B, completeMessage[messageBitCounter].ToString(), bitPlaneSelector); stegoImage.Image.SetPixel(currentPixelXValue, currentPixelYValue, Color.FromArgb(pixel.R, pixel.G, color)); // If the whole bit plane should be filled before a pixel should be reused if (bitPlanesFirst) { // Go to the next pixel currentPixel += pixelDistance; // If the pixel exceeds the maximum amount of pixels // go to the next rest class if (currentPixel >= maxCarrierPixels) { currentPixel = ++restClassCounter; // If all rest classes are exhausted, // begin at pixel 0 again but go to the next bit plane if (restClassCounter >= pixelDistance) { currentPixel = 0; restClassCounter = 0; bitPlaneSelector++; } } // If a pixel should be used in full before the next pixel is chosen } else { // Go to the next bit plane bitPlaneSelector++; // If all allowed bits of a single pixel are used // go to the next pixel if (bitPlaneSelector >= bitPlanes) { currentPixel += pixelDistance; bitPlaneSelector = 0; // If the pixel exceeds the maximum amount of pixels // go to the next rest class if (currentPixel >= maxCarrierPixels) { currentPixel = ++restClassCounter; } } // if } // else break; } // switch messageBitCounter++; } // If a password has been used, scramble back the image if (passwordSet) { stegoImage.Image = ImageScrambler.ScrambleOne(ImageScrambler.ScrambleThree(ImageScrambler.ScrambleTwo(stegoImage.Image))); } return(stegoImage); }
/// <summary> /// Extracts a <see cref="StegoMessage"/> object from a /// <see cref="StegoImage"/> object. /// </summary> /// <param name="stegoImage">The stego image which hides the message</param> /// <param name="stegoPassword">The password used to hide the message (Randomized Hide and Seek)</param> /// <param name="bitPlanes">The amount of bit planes that have been used to hide the message</param> /// <param name="bitPlanesFirst">true for bit planes first-mode and false for pixels first-mode</param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="System.Reflection.TargetInvocationException"></exception> /// <exception cref="FormatException"></exception> /// <exception cref="OverflowException"></exception> /// <exception cref="DecoderFallbackException"></exception> /// <exception cref="Exception"></exception> /// <returns>The message that has been hidden inside the stego image</returns> public StegoMessage ExtractMessage(StegoImage stegoImage, string stegoPassword, int bitPlanes, bool bitPlanesFirst) { // Generate new stego image object to omit f*****g pass by reference Bitmap stegoImageCopy = new Bitmap(stegoImage.Image); // Set extraction option bool passwordSet = !(stegoPassword.Equals("")); // Base variable declaration StringBuilder messageNameBuilder = new StringBuilder(); StringBuilder payloadSizeBuilder = new StringBuilder(); StringBuilder payloadBuilder = new StringBuilder(); int stegoWidth = stegoImageCopy.Width; int stegoHeight = stegoImageCopy.Height; uint maxStegoPixels = (uint)(stegoWidth * stegoHeight); uint pixelDistance = 1; // If a stego password is specified if (passwordSet) { // Transform the password into a value defining the distance between pixels byte[] passwordBytes = Encoding.UTF8.GetBytes(stegoPassword); passwordBytes = Hasher.HashSha256(passwordBytes); pixelDistance = (uint)((ulong)BitConverter.ToInt64(passwordBytes, 0) % maxStegoPixels); // Scramble image stegoImageCopy = ImageScrambler.ScrambleOne(stegoImageCopy); stegoImageCopy = ImageScrambler.ScrambleThree(stegoImageCopy); stegoImageCopy = ImageScrambler.ScrambleTwo(stegoImageCopy); } // Variables for LSB extraction int messageBitCounter = 0; // Counter iterating over all message bits Color pixel; // Pixel object used to generate the new color int currentPixelXValue; // x coordinate of current pixel int currentPixelYValue; // y coordinate of current pixel uint currentPixel = 0; // Variable storing the currently considered pixel uint restClassCounter = 0; // Counter iterating over all rest classes byte bitPlaneSelector = 0; // Variable used to select which bit plane is used for embedding // Extract the first 512 bits which encode the message's name while (messageBitCounter < 512) { // Get current pixel currentPixelXValue = (int)currentPixel % stegoWidth; currentPixelYValue = (int)currentPixel / stegoWidth; pixel = stegoImageCopy.GetPixel(currentPixelXValue, currentPixelYValue); switch (messageBitCounter % 3) { case 0: messageNameBuilder.Append(GetBit(pixel.R, bitPlaneSelector)); break; case 1: messageNameBuilder.Append(GetBit(pixel.G, bitPlaneSelector)); break; case 2: messageNameBuilder.Append(GetBit(pixel.B, bitPlaneSelector)); // If the whole bit plane should be filled before a pixel should be reused if (bitPlanesFirst) { // Go to the next pixel currentPixel += pixelDistance; // If the pixel exceeds the maximum amount of pixels // go to the next rest class if (currentPixel >= maxStegoPixels) { currentPixel = ++restClassCounter; // If all rest classes are exhausted, // begin at pixel 0 again but go to the next bit plane if (restClassCounter >= pixelDistance) { currentPixel = 0; restClassCounter = 0; bitPlaneSelector++; } } // If a pixel should be used in full before the next pixel is chosen } else { // Go to the next bit plane bitPlaneSelector++; // If all allowed bits of a single pixel are used // go to the next pixel if (bitPlaneSelector >= bitPlanes) { currentPixel += pixelDistance; bitPlaneSelector = 0; // If the pixel exceeds the maximum amount of pixels // go to the next rest class if (currentPixel >= maxStegoPixels) { currentPixel = ++restClassCounter; } } // if } // else break; } // switch messageBitCounter++; } // Compose the message's name string messageNameString = messageNameBuilder.ToString(); string messageName = Converter.BinaryToString(messageNameString).Replace("\0", ""); // Extract the next 24 bits which encode the message's payload size while (messageBitCounter < 536) { // Get current pixel currentPixelXValue = (int)currentPixel % stegoWidth; currentPixelYValue = (int)currentPixel / stegoWidth; pixel = stegoImageCopy.GetPixel(currentPixelXValue, currentPixelYValue); switch (messageBitCounter % 3) { case 0: payloadSizeBuilder.Append(GetBit(pixel.R, bitPlaneSelector)); break; case 1: payloadSizeBuilder.Append(GetBit(pixel.G, bitPlaneSelector)); break; case 2: payloadSizeBuilder.Append(GetBit(pixel.B, bitPlaneSelector)); // If the whole bit plane should be filled before a pixel should be reused if (bitPlanesFirst) { // Go to the next pixel currentPixel += pixelDistance; // If the pixel exceeds the maximum amount of pixels // go to the next rest class if (currentPixel >= maxStegoPixels) { currentPixel = ++restClassCounter; // If all rest classes are exhausted, // begin at pixel 0 again but go to the next bit plane if (restClassCounter >= pixelDistance) { currentPixel = 0; restClassCounter = 0; bitPlaneSelector++; } } // If a pixel should be used in full before the next pixel is chosen } else { // Go to the next bit plane bitPlaneSelector++; // If all allowed bits of a single pixel are used // go to the next pixel if (bitPlaneSelector >= bitPlanes) { currentPixel += pixelDistance; bitPlaneSelector = 0; // If the pixel exceeds the maximum amount of pixels // go to the next rest class if (currentPixel >= maxStegoPixels) { currentPixel = ++restClassCounter; } } // if } // else break; } // switch messageBitCounter++; } // Compose the payloads's size string payloadSizeString = payloadSizeBuilder.ToString(); uint payloadSize = Converter.BinaryToUint(payloadSizeString); // Extract the payload while (messageBitCounter < payloadSize + 536) { // Get current pixel currentPixelXValue = (int)currentPixel % stegoWidth; currentPixelYValue = (int)currentPixel / stegoWidth; pixel = stegoImageCopy.GetPixel(currentPixelXValue, currentPixelYValue); switch (messageBitCounter % 3) { case 0: payloadBuilder.Append(GetBit(pixel.R, bitPlaneSelector)); break; case 1: payloadBuilder.Append(GetBit(pixel.G, bitPlaneSelector)); break; case 2: payloadBuilder.Append(GetBit(pixel.B, bitPlaneSelector)); // If the whole bit plane should be filled before a pixel should be reused if (bitPlanesFirst) { // Go to the next pixel currentPixel += pixelDistance; // If the pixel exceeds the maximum amount of pixels // go to the next rest class if (currentPixel >= maxStegoPixels) { currentPixel = ++restClassCounter; // If all rest classes are exhausted, // begin at pixel 0 again but go to the next bit plane if (restClassCounter >= pixelDistance) { currentPixel = 0; restClassCounter = 0; bitPlaneSelector++; } } // If a pixel should be used in full before the next pixel is chosen } else { // Go to the next bit plane bitPlaneSelector++; // If all allowed bits of a single pixel are used // go to the next pixel if (bitPlaneSelector >= bitPlanes) { currentPixel += pixelDistance; bitPlaneSelector = 0; // If the pixel exceeds the maximum amount of pixels // go to the next rest class if (currentPixel >= maxStegoPixels) { currentPixel = ++restClassCounter; } } // if } // else break; } // switch messageBitCounter++; } // Compose the message object string payloadString = payloadBuilder.ToString(); byte[] payload = payloadString.ConvertBitstringToByteArray(); StegoMessage message = new StegoMessage(messageName, payload); return(message); }