# 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

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

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