Algorithm principle
Digital Watermark is a kind of protective information embedded in carrier files by computer algorithm. Digital Watermark technology is a computer information hiding technology based on content and non cryptographic mechanism. It embeds some identification information (i.e. Digital Watermark) directly into digital carriers (including multimedia, documents, software, etc.) or indirectly (modify the structure of a specific area), which does not affect the use value of the original carrier, and is not easy to be detected and modified again, but can be identified and recognized by the manufacturer.
There are many kinds of digital watermarking commonly used, and LSB is one of the simple steganography algorithms.
The full name of LSB is Least Significant Bit (LSB), which is often used as image steganography algorithm (her figure is often seen in CTF) LSB is a kind of spatial algorithm. It embeds information into the lowest bit of pixel bits in image points to ensure that the embedded information is invisible. However, due to the use of unimportant pixel bits of the image, the robustness of the algorithm is poor, and the watermark information is easy to be damaged by filtering, image quantization and geometric deformation.
Algorithm implementation
1. Separate all pixels in the image file in RGB, and convert each color component into binary representation
2. Set the last bit of the RGB color channel component value corresponding to each pixel of the image to 0, which has a very slight impact on the image and will not affect the judgment of the human eye on the image
3. Information embedding: convert the information file into binary string, encrypt the binary string corresponding to the file carrier into ciphertext string by entering the key and performing AES encryption algorithm, and fill these bit information into the lowest bit of the component of the RGB channel of the target carrier picture in turn to complete the information embedding
4. Information extraction: extract the lowest bit of the RGB channel of the image pixels in turn, splice them, decrypt the bit stream of the file content through the corresponding aes key, and restore the original file
flow chart
Embedded process
Extraction process
Introduction to key contents of embedding algorithm
Calculate the number of pixels X of the carrier picture and the Width of the picture
Take 64bit from SHA256 hash value and generate shaping data Y
Calculate Z = Y mod X
Define variables CurrentPixel=0, RestCounter=0
Then the carrier pixel position currently processed is:
(Pixel_X,Pixel_Y)=(CurrentPixel mod Width,CurrentPixel / Width )
The bit data of the file is embedded according to the lowest bit of the component values of the red, green and blue channels at the current pixel position
After processing the current pixel, jump to the next position of the specified length
CurrentPixel+=Z
The embedding operation is repeated until the next position of the processed pixel exceeds the last pixel of the carrier image,
CurrentPixel=++RestCounter
At this time, the position of the processing pixel jumps to the next bit of the pixel position processed for the first time.
Since the capacity compatibility between the information file and the carrier is determined in advance, the position of the maximum pixel of the carrier will not be exceeded in the end
Introduction to key contents of extraction algorithm
In this program, the content embedded in the carrier image includes three parts
(1) Name of the file
(2) Bitstream length of file content
(3) Bitstream of file content
The file name is 512bit, the file bit stream length is 24bit, and their length is fixed.
The length Size of the file can be calculated by extracting a fixed length of 24bit
Then, to extract the contents of the file, you only need to extract the bitstream of Size in turn after extracting the name and bitstream length
[0512]: file name
[512536): File bitstream length
[536536 + size): File bitstream
The process of extracting the lowest bit from the carrier is similar to that of embedding the lowest bit in the embedding algorithm.
Format of file name "xxx.png"
Function: xxx is also a part of the file information and needs to be completely restored
png is the format of the file, and it also needs to be restored according to the format of the original file when saving
Program demonstration
Comparison of image similarity before and after embedding
The extracted hidden image is consistent with the original image without distortion
Embed information in other formats, taking txt documents as an example
Comparison of similarity before and after embedding
The extracted txt file is compared with the source file
Safety analysis
1. The security of information should not depend on the confidentiality of algorithm, but on the confidentiality of key.
This program is encrypted symmetrically by AES. Only after the corresponding password is negotiated in advance by the two communication engines can the hidden watermark file be extracted, which realizes the confidentiality of information to a certain extent.
2. If the other party doesn't master the key, it can't decrypt the watermark, but it can modify the carrier image by itself, which will also cause the tampering of the watermark information to the extent that it can't be found by the naked eye. If the attacker master the secret key, it can tamper with the file information and destroy the integrity of the information.
3. Analyze the similarity indexes of pictures before and after encryption by program: PNSR and MSE
However, in order to meet the invisibility of embedded information, the intensity of embedded information is allowed to be low and less content can be stored.
4. Only simple stream format files can be processed
5. Tested several carrier image formats: PNG, BMP, ICO, JPG, GIF, JPEG
It is found that the carrier image is incompatible with JPEG and GIF formats, and the compatibility is poor.
Partial code display
using Spatial replacement Information Steganography Algorithm.Helper; using Spatial replacement Information Steganography Algorithm.StegoLogic; using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Text; using Spatial replacement Information Steganography Algorithm.Exceptions; using Spatial replacement Information Steganography Algorithm.Encryption; using Spatial replacement Information Steganography Algorithm.DataStructures; namespace LsbStego.StegoLogic { internal class LsbEmbedder { #region Singleton definition private static LsbEmbedder instance; private LsbEmbedder() { } public static LsbEmbedder Instance { get { if (instance == null) { instance = new LsbEmbedder(); } return instance; } } #endregion #region Methods used to rate a carrier /// <summary> /// Calculates the Hamming distance between a full message (not only the payload) /// and the LSBs of an image and returns them as integer /// </summary> /// <param name="carrierImage"></param> /// <param name="message"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="MessageTooBigException"></exception> /// <returns></returns> //Calculate Hamming distance through complete information (without password) public int CalculateHammingDistance(Bitmap carrierImage, string completeMessage) { // Get all necessary information about the carrier uint carrierWidth = (uint)carrierImage.Width; uint carrierHeight = (uint)carrierImage.Height; uint maxCarrierPixels = (carrierWidth * carrierHeight); uint capacity = (3 * maxCarrierPixels); // Pipe the exception handling a level higher if (completeMessage.Length > capacity) { throw new MessageTooBigException(); } // Collect the LSBs of the scrambled carrier string carrierLsbs = CollectCarrierLsbs(carrierImage); // Calculate the Hamming distance int hammingDistance = 0; int lsbCounter = 0; foreach (char bit in completeMessage) { // If the index of an array reaches 2^31 it needs to be resetted // This is because an array is accessible only by int values if (lsbCounter == int.MaxValue) { carrierLsbs = carrierLsbs.Substring(lsbCounter); lsbCounter = 0; } // Increase Hamming distance if message bit and LSB do not match if (!Char.Equals(bit, carrierLsbs[lsbCounter])) { hammingDistance++; } lsbCounter++; } return hammingDistance; } /// <summary> /// Calculates the Hamming distance between a full message (not only the payload) /// and the LSBs of an image and returns them as integer /// </summary> /// <param name="carrierImage"></param> /// <param name="message"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="MessageTooBigException"></exception> /// <returns></returns> public int CalculateHammingDistance(Bitmap carrierImage, string completeMessage, string password) { // If no password is specified, the default routine should be used if (password == null || password.Equals("")) { return CalculateHammingDistance(carrierImage, completeMessage); } // Get all necessary information about the carrier uint carrierWidth = (uint)carrierImage.Width; uint carrierHeight = (uint)carrierImage.Height; uint maxCarrierPixels = (carrierWidth * carrierHeight); uint capacity = (3 * maxCarrierPixels); // Pipe the exception handling a level higher if (completeMessage.Length > capacity) { throw new MessageTooBigException(); } // Scramble image // Transform the password into a value defining the distance between pixels byte[] passwordBytes = Encoding.UTF8.GetBytes(password); passwordBytes = Hasher.HashSha256(passwordBytes); uint pixelDistance = (uint)((ulong)BitConverter.ToInt64(passwordBytes, 0) % maxCarrierPixels); //uint pixelDistance = 0; //foreach (byte by in passwordBytes) { // pixelDistance += by; //} //pixelDistance = (uint) (pixelDistance % maxCarrierPixels); // Variables int hammingDistance = 0; // Current hamming distance Color pixel; // Pixel object used to generate the new color int currentPixelX; // x coordinate of current pixel int currentPixelY; // y coordinate of current pixel uint currentPixel = 0; // Variable storing the currently considered pixel uint restClassCounter = 0; // Counter iterating over all rest classes int messageBitCounter = 0; // Counter iterating over all message bits foreach (char bit in completeMessage) { // Get current pixel,6666666666666 currentPixelX = (int)(currentPixel % carrierWidth); currentPixelY = (int)(currentPixel / carrierWidth); pixel = carrierImage.GetPixel(currentPixelX, currentPixelY); // Define which of the three LSBs of a pixel should be checked char extractedLsb = 'F'; switch (messageBitCounter % 3) { case 0: extractedLsb = ExtractLsbAsChar(pixel.R); break; case 1: extractedLsb = ExtractLsbAsChar(pixel.G); break; case 2: extractedLsb = ExtractLsbAsChar(pixel.B); // Go to the next pixel currentPixel += pixelDistance; if (currentPixel >= maxCarrierPixels) { currentPixel = ++restClassCounter; } break; default: break; } messageBitCounter++; // Increase Hamming distance if message bit and LSB do not match if (!Char.Equals(bit, extractedLsb)) { hammingDistance++; } } return hammingDistance; } #endregion //Calculate Hamming distance through complete information with password #region Hiding and extracting a message /// <summary> /// Hides a given message (file) inside a given carrier (image) /// </summary> /// <param name="carrierImage"></param> /// <param name="payload"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="EncoderFallbackException"></exception> /// <exception cref="FormatException"></exception> /// <exception cref="OverflowException"></exception> /// <exception cref="MessageNameTooBigException"></exception> /// <exception cref="Exception"></exception> /// <returns></returns> //Embed message in picture (without password) public Bitmap HideMessage(Bitmap carrierImage, Message message) { // Base variable declaration Scanner scanner = Scanner.Instance; Bitmap stegoImage = carrierImage; int carrierWidth = carrierImage.Width; int carrierHeight = carrierImage.Height; // 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 = scanner.CalculateCapacity(carrierImage, "bits"); //Gets the number of pixel bits of the picture if (completeMessageLength > carrierCapacity) { throw new MessageTooBigException(); } // Hiding variables Color pixel; int pixelX = 0; int pixelY = 0; int messageBitCounter = 0; byte color = 0x00; // While there is something left to hide while (messageBitCounter < completeMessageLength) { // Get current pixel pixel = stegoImage.GetPixel(pixelX, pixelY); // Define which of the three LSBs of a pixel should be used switch (messageBitCounter % 3) { case 0: color = InsertMessageBit(pixel.R, completeMessage[messageBitCounter].ToString()); stegoImage.SetPixel(pixelX, pixelY, Color.FromArgb(color, pixel.G, pixel.B)); break; case 1: color = InsertMessageBit(pixel.G, completeMessage[messageBitCounter].ToString()); stegoImage.SetPixel(pixelX, pixelY, Color.FromArgb(pixel.R, color, pixel.B)); break; case 2: color = InsertMessageBit(pixel.B, completeMessage[messageBitCounter].ToString()); stegoImage.SetPixel(pixelX, pixelY, Color.FromArgb(pixel.R, pixel.G, color)); // Get next pixel pixelX++; if (pixelX >= carrierWidth) { pixelX = 0; pixelY++; } break; default: break; } messageBitCounter++; } return stegoImage; } /// <summary> /// Hides a given message inside a given carrier (image). /// Thereby, a password indicates the distance between used pixels /// </summary> /// <param name="carrierImage"></param> /// <param name="payload"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="EncoderFallbackException"></exception> /// <exception cref="FormatException"></exception> /// <exception cref="OverflowException"></exception> /// <exception cref="MessageNameTooBigException"></exception> /// <exception cref="Exception"></exception> /// <returns></returns> public Bitmap HideMessage(Bitmap carrierImage, Message message, string password) { Scanner scanner = Scanner.Instance; Bitmap stegoImage = carrierImage; uint carrierWidth = (uint)carrierImage.Width; uint carrierHeight = (uint)carrierImage.Height; ulong maxCarrierPixels = (carrierWidth * carrierHeight); string completeMessage = GenerateMessageBitPattern(message); uint completeMessageLength = (uint)completeMessage.Length; uint carrierCapacity = scanner.CalculateCapacity(carrierImage, "bits"); if (completeMessageLength > carrierCapacity) { throw new MessageTooBigException(); } byte[] passwordBytes = Encoding.UTF8.GetBytes(password); passwordBytes = Hasher.HashSha256(passwordBytes); uint pixelDistance = (uint)((ulong)BitConverter.ToInt64(passwordBytes, 0) % maxCarrierPixels); int messageBitCounter = 0; Color pixel; byte color = 0x00; int currentPixelX; int currentPixelY; uint currentPixel = 0; uint restClassCounter = 0; while (messageBitCounter < completeMessageLength) { // Calculate pixel position currentPixelX = (int)(currentPixel % carrierWidth); currentPixelY = (int)(currentPixel / carrierWidth); pixel = stegoImage.GetPixel(currentPixelX, currentPixelY); // Bit information is successively embedded in the bits of the carrier channel switch (messageBitCounter % 3) { case 0: color = InsertMessageBit(pixel.R, completeMessage[messageBitCounter].ToString()); stegoImage.SetPixel(currentPixelX, currentPixelY, Color.FromArgb(color, pixel.G, pixel.B)); break; case 1: color = InsertMessageBit(pixel.G, completeMessage[messageBitCounter].ToString()); stegoImage.SetPixel(currentPixelX, currentPixelY, Color.FromArgb(pixel.R, color, pixel.B)); break; case 2: color = InsertMessageBit(pixel.B, completeMessage[messageBitCounter].ToString()); stegoImage.SetPixel(currentPixelX, currentPixelY, Color.FromArgb(pixel.R, pixel.G, color)); // Go to the next pixel currentPixel += pixelDistance; if (currentPixel >= maxCarrierPixels) { currentPixel = ++restClassCounter; } break; default: break; } messageBitCounter++; } return stegoImage; } //Embed the message message with password in the picture /// <summary> /// Extracts a message from a stego image and returns it as byte array /// </summary> /// <param name="stegoImage"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="Exception"></exception> /// <exception cref="FormatException"></exception> /// <exception cref="OverflowException"></exception> /// <exception cref="DecoderFallbackException"></exception> /// <returns></returns> //Export Message through password and picture public Message ExtractMessage(Bitmap stegoImage, string password) { // If no password is specified, the default routine should be used // because it has been well tested and should be more robust if (password == null || password.Equals("")) { return ExtractMessage(stegoImage); } // Base variable declaration StringBuilder messageNameBuilder = new StringBuilder(); StringBuilder payloadSizeBuilder = new StringBuilder(); StringBuilder payloadBuilder = new StringBuilder(); int stegoWidth = stegoImage.Width; int stegoHeight = stegoImage.Height; long maxStegoPixels = (stegoWidth * stegoHeight); // Transform the password into a value defining the distance between pixels byte[] passwordBytes = Encoding.UTF8.GetBytes(password); passwordBytes = Hasher.HashSha256(passwordBytes); uint pixelDistance = (uint)((ulong)BitConverter.ToInt64(passwordBytes, 0) % (uint)maxStegoPixels); int messageBitCounter = 0; // Counter iterating over all message bits Color pixel; // Pixel object used to generate the new color int currentPixelX; // x coordinate of current pixel int currentPixelY; // y coordinate of current pixel uint currentPixel = 0; // Variable storing the currently considered pixel uint restClassCounter = 0; // Counter iterating over all rest classes uint payloadSize = 0; // Variable indicating the size of the payload string messageName = ""; // String storing the name of the message // Extract the first 512 bits which encode // the message's payload size and the message's name while (messageBitCounter < 512) { // Get current pixel currentPixelX = (int)currentPixel % stegoWidth; currentPixelY = (int)currentPixel / stegoWidth; pixel = stegoImage.GetPixel(currentPixelX, currentPixelY); switch (messageBitCounter % 3) { case 0: messageNameBuilder.Append(ExtractLsbAsString(pixel.R)); break; case 1: messageNameBuilder.Append(ExtractLsbAsString(pixel.G)); break; case 2: messageNameBuilder.Append(ExtractLsbAsString(pixel.B)); // Go to the next pixel currentPixel += pixelDistance; if (currentPixel >= maxStegoPixels) { currentPixel = ++restClassCounter; } break; default: break; } messageBitCounter++; } // Compose the message's name string messageNameString = messageNameBuilder.ToString(); messageName = Converter.BinaryToString(messageNameString).Replace("\0", ""); // Extract the payload's size while (messageBitCounter < 536) { // Get current pixel currentPixelX = (int)currentPixel % stegoWidth; currentPixelY = (int)currentPixel / stegoWidth; pixel = stegoImage.GetPixel(currentPixelX, currentPixelY); switch (messageBitCounter % 3) { case 0: payloadSizeBuilder.Append(ExtractLsbAsString(pixel.R)); break; case 1: payloadSizeBuilder.Append(ExtractLsbAsString(pixel.G)); break; case 2: payloadSizeBuilder.Append(ExtractLsbAsString(pixel.B)); // Go to the next pixel currentPixel += pixelDistance; if (currentPixel >= maxStegoPixels) { currentPixel = ++restClassCounter; } break; default: break; } messageBitCounter++; } // Compose the payloads's size string payloadSizeString = payloadSizeBuilder.ToString(); payloadSize = Converter.BinaryToUint(payloadSizeString); // Extract the payload while (messageBitCounter < payloadSize + 536) { // Get current pixel currentPixelX = (int)currentPixel % stegoWidth; currentPixelY = (int)currentPixel / stegoWidth; pixel = stegoImage.GetPixel(currentPixelX, currentPixelY); switch (messageBitCounter % 3) { case 0: payloadBuilder.Append(ExtractLsbAsString(pixel.R)); break; case 1: payloadBuilder.Append(ExtractLsbAsString(pixel.G)); break; case 2: payloadBuilder.Append(ExtractLsbAsString(pixel.B)); // Go to the next pixel currentPixel += pixelDistance; if (currentPixel >= maxStegoPixels) { currentPixel = ++restClassCounter; } break; default: break; } messageBitCounter++; } // Compose the message object string payloadString = payloadBuilder.ToString(); byte[] payload = Extensions.ConvertBitstringToByteArray(payloadString); Message message = new Message(messageName, payload, payloadSize); return message; } /// <summary> /// Extracts a message from a stego image and returns it as byte array /// </summary> /// <param name="stegoImage"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="Exception"></exception> /// <exception cref="FormatException"></exception> /// <exception cref="OverflowException"></exception> /// <exception cref="DecoderFallbackException"></exception> /// <returns></returns> //Export Message through picture (without password) public Message ExtractMessage(Bitmap stegoImage) { // Base variable declaration StringBuilder messageNameBuilder = new StringBuilder(); StringBuilder payloadSizeBuilder = new StringBuilder(); StringBuilder payloadBuilder = new StringBuilder(); int stegoWidth = stegoImage.Width; int stegoHeight = stegoImage.Height; // Variables for LSB extraction Color pixel; int pixelX = 0; int pixelY = 0; int messageBitCounter = 0; uint payloadSize = 0; string messageName = ""; // Extract the first 512 bits which encode // the message's payload size and the message's name while (messageBitCounter < 512) { pixel = stegoImage.GetPixel(pixelX, pixelY); switch (messageBitCounter % 3) { case 0: messageNameBuilder.Append(ExtractLsbAsString(pixel.R)); break; case 1: messageNameBuilder.Append(ExtractLsbAsString(pixel.G)); break; case 2: messageNameBuilder.Append(ExtractLsbAsString(pixel.B)); pixelX++; if (pixelX >= stegoImage.Width) { pixelX = 0; pixelY++; } break; default: break; } messageBitCounter++; } // Compose the message's name string messageNameString = messageNameBuilder.ToString(); messageName = Converter.BinaryToString(messageNameString).Replace("\0", ""); // Extract the payload's size while (messageBitCounter < 536) { pixel = stegoImage.GetPixel(pixelX, pixelY); switch (messageBitCounter % 3) { case 0: payloadSizeBuilder.Append(ExtractLsbAsString(pixel.R)); break; case 1: payloadSizeBuilder.Append(ExtractLsbAsString(pixel.G)); break; case 2: payloadSizeBuilder.Append(ExtractLsbAsString(pixel.B)); pixelX++; if (pixelX >= stegoImage.Width) { pixelX = 0; pixelY++; } break; default: break; } messageBitCounter++; } // Compose the payloads's size string payloadSizeString = payloadSizeBuilder.ToString(); payloadSize = Converter.BinaryToUint(payloadSizeString); // Extract the payload while (messageBitCounter < 536 + payloadSize) { pixel = stegoImage.GetPixel(pixelX, pixelY); switch (messageBitCounter % 3) { case 0: payloadBuilder.Append(ExtractLsbAsString(pixel.R)); break; case 1: payloadBuilder.Append(ExtractLsbAsString(pixel.G)); break; case 2: payloadBuilder.Append(ExtractLsbAsString(pixel.B)); pixelX++; if (pixelX >= stegoImage.Width) { pixelX = 0; pixelY++; } break; default: break; } messageBitCounter++; } // Compose the message object string payloadString = payloadBuilder.ToString(); byte[] payload = Extensions.ConvertBitstringToByteArray(payloadString); Message message = new Message(messageName, payload, payloadSize); return message; } /// <summary> /// Generates a binary bitstring from a message object /// </summary> /// <param name="message"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <returns></returns> //Convert Message to string public string GenerateMessageBitPattern(Message message) { // Extract data from the message object string messageName = message.Name; byte[] payload = message.Payload; uint payloadSize = message.PayloadSize; // Convert data to binary strings string payloadNameBinary = Converter.StringToBinary(messageName, 64); string payloadSizeBinary = Converter.DecimalToBinary(payloadSize, 24); string payloadBinary = Converter.ByteArrayToBinary(payload); // Generate complete message and split it to 3 bits per pixel StringBuilder sb = new StringBuilder(); sb.Append(payloadNameBinary); sb.Append(payloadSizeBinary); sb.Append(payloadBinary); return sb.ToString(); } /// <summary> /// Inserts a new LSB into an arbitrary byte value /// </summary> /// <param name="carrierByte"></param> /// <param name="messsageBit"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="FormatException"></exception> /// <exception cref="OverflowException"></exception> /// <returns></returns> //Embedding information bits into LSB private byte InsertMessageBit(byte carrierByte, string messsageBit) { StringBuilder sb = new StringBuilder(Converter.DecimalToBinary(carrierByte, 8)); sb.Remove(sb.Length - 1, 1); sb.Append(messsageBit); byte stegoByte = Converter.BinaryToByte(sb.ToString()); return stegoByte; } /// <summary> /// Extracts the LSB from a byte and returns it as string /// </summary> /// <param name="inputByte"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <returns></returns> //Output the last bit of byte object as string private string ExtractLsbAsString(byte inputByte) { return ((inputByte % 2) == 0) ? "0" : "1"; } /// <summary> /// Extracts the LSB from a byte and returns it as char /// </summary> /// <param name="inputByte"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="FormatException"></exception> /// <returns></returns> //Output the last bit of byte object to char private char ExtractLsbAsChar(byte inputByte) { string bitPattern = Converter.DecimalToBinary(inputByte, 8); string lsb = bitPattern.Substring(7, 1); return Convert.ToChar(lsb); } /// <summary> /// Collects all LSBs of a given carrier image ordered from pixel /// (0, 0) to (xMax, yMax) and from color R over G to B and returns them as string /// </summary> /// <param name="carrierImage"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <returns></returns> //Convert picture information into string form public string CollectCarrierLsbs(Bitmap carrierImage) { StringBuilder sb = new StringBuilder(); int width = carrierImage.Width; int height = carrierImage.Height; Color pixel; // Iterate over the whole image for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { pixel = carrierImage.GetPixel(x, y); sb.Append(ExtractLsbAsString(pixel.R)); sb.Append(ExtractLsbAsString(pixel.G)); sb.Append(ExtractLsbAsString(pixel.B)); } } return sb.ToString(); } #endregion #region Methods that access the file system /// <summary> /// Writes the stego image to the given path /// </summary> /// <param name="stegoImage"></param> /// <param name="path"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="PathTooLongException"></exception> /// <exception cref="IOException"></exception> /// <exception cref="FileNotFoundException"></exception> /// <exception cref="PathTooLongException"></exception> /// <exception cref="DirectoryNotFoundException"></exception> /// <exception cref="NotSupportedException"></exception> /// <exception cref="System.Security.SecurityException"></exception> /// <exception cref="System.Runtime.InteropServices.ExternalException"></exception> public void WriteStegoImage(Bitmap stegoImage, string path) { // Retrieve the image extension from the destination path string extension = Path.GetExtension(path); // Set the image format used to write the stego file ImageFormat format; switch (extension) { case ".png": format = ImageFormat.Png; break; case ".jpeg": format = ImageFormat.Jpeg; break; case ".bmp": format = ImageFormat.Bmp; break; case ".gif": format = ImageFormat.Gif; break; default: format = ImageFormat.Png; break; } using (FileStream fs = new FileStream(path, FileMode.Create)) { stegoImage.Save(fs, format); //fs.Dispose(); //fs.Close(); } } /// <summary> /// Writes an arbitrary message to the drive /// </summary> /// <param name="message"></param> /// <param name="path"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="PathTooLongException"></exception> /// <exception cref="IOException"></exception> /// <exception cref="FileNotFoundException"></exception> /// <exception cref="PathTooLongException"></exception> /// <exception cref="DirectoryNotFoundException"></exception> /// <exception cref="NotSupportedException"></exception> /// <exception cref="System.Security.SecurityException"></exception> /// <exception cref="ObjectDisposedException"></exception> public void WriteMessage(byte[] message, string path) { //var fs = new FileStream(path, FileMode.Create); //fs.Write(message, 0, message.Length); //fs.Dispose(); //fs.Close(); using (var fs = new FileStream(path, FileMode.Create)) { fs.Write(message, 0, message.Length); } } #endregion } }