C# implement LSB airspace replacement Information Steganography

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
	}
}

Tags: C# image processing Encryption

Posted on Sun, 12 Sep 2021 20:16:17 -0400 by belphegor