C#实现LSB空域替换信息隐写


算法原理

数字水印(Digital Watermark)是一种应用计算机算法嵌入载体文件的保护信息,数字水印技术是一种基于内容的、非密码机制的计算机信息隐藏技术,它是将一些标识信息(即数字水印)直接嵌入数字载体当中(包括多媒体、文档、软件等)或是间接表示(修改特定区域的结构),且不影响原载体的使用价值,也不容易被探知和再次修改,但可以被生产方识别和辨认。
常用的数字水印有很多种,LSB是其中一种简单的隐写算法。
LSB全称为 Least Significant Bit(最低有效位),是一种常被用做图片隐写的算法(在CTF中经常见到她的身影)。LSB属于空域算法中的一种,是将信息嵌入到图像点中像素位的最低位,以保证嵌入的信息是不可见的,但是由于使用了图像不重要的像素位,算法的鲁棒性差,水印信息很容易为滤波、图像量化、几何变形的操作破坏。

算法实现

1.将图像文件中的所有像素点以RGB形式分隔开,并将各个颜色分量转换成二进制表示
2.把图像每个像素点对应的RGB颜色通道分量值的最后一位全部设置成0,对图像得影响非常细微,不会影响人眼对图片的判断
3.信息嵌入:将信息文件转化为二进制字符串,并通过输入密钥并进行AES加密算法将文件载体对应的二进制字符串加密成密文串,并将这些比特信息依次填入目标载体图片RGB通道的分量的最低位上,完成信息的嵌入
4.信息提取:将图像像素的RGB通道的最低位依次提取出来,并进行拼接,通过对应的aes密钥解密文件内容的比特流,还原出原始文件

流程图

嵌入流程

在这里插入图片描述

提取流程

在这里插入图片描述

嵌入算法关键内容介绍

计算载体图片的像素个数X,图片的宽度Width
从SHA256哈希值中取64bit并生成整形数据Y
计算 Z = Y mod X
定义变量CurrentPixel=0,RestCounter=0
则当前处理的载体像素位置为:
(Pixel_X,Pixel_Y)=(CurrentPixel mod Width,CurrentPixel / Width )
依次按当前像素位置的红、绿、蓝通道的分量值的最低位嵌入文件的比特数据
处理完当前像素后,跳转到下一个指定长度的位置
CurrentPixel+=Z
重复嵌入操作直到所处理的像素的下一条位置超出载体图像的最后一个像素时,
CurrentPixel=++RestCounter
此时处理像素的位置跳转到第一次处理的像素位置的后一位。
由于事先判断了信息文件和载体的容量兼容性,因此最终不会超出载体的最大像素的位置

提取算法关键内容介绍

本程序中,嵌入在载体图片中的内容包括三个部分
(1)文件的名称
(2)文件内容的比特流长度
(3)文件内容的比特流
其中文件名称是512bit,文件比特流长度是24bit,它们的长度是固定的。
通过提取固定长度的24bit可以计算出该文件的长度Size
那么提取文件的内容只需要在提取完名称和比特流长度后依次提取Size个长度的比特流
[0,512):文件名称
[512,536):文件比特流长度
[536,536+Size):文件比特流
从载体中提取最低比特位的过程与嵌入算法中嵌入最低位比特的过程类似。
文件名称的格式“xxx.png”
作用:xxx也是文件的信息的一部分,也需要完整还原
png是文件的格式,保存时也需要按原文件的格式还原

程序演示

在这里插入图片描述
嵌入前后的图像相似度对比
在这里插入图片描述
在这里插入图片描述
提取的隐藏图片与原图一致,没有失真
在这里插入图片描述
嵌入其他格式的信息,以txt文档为例子
在这里插入图片描述
嵌入前后相似度对比
在这里插入图片描述
提取后的txt文件与源文件对比
在这里插入图片描述

安全性分析

1.信息的安全性不应该依赖于算法的保密而应该依赖于密钥的保密。
本程序通过AES对称加密,只有通信双发预先协商好对应的密码后才能提取出隐藏的水印文件,在一定程度上实现了信息的保密性。
2.通信对方若不掌握密钥不能解密出水印,但是可以自己修改载体图像,也会在肉眼发现不了的程度上造成水印信息的篡改,倘若攻击者掌握了秘钥是可以篡改文件信息的,破坏了信息的完整性。
3.通过程序分析加密前后图片的相似度指标:PNSR 、MSE
均在合理范围内,人眼也不会察觉到。但是为了满足嵌入信息的不可见性,允许嵌入的信息强度较低,能够存放的内容较少。
4.只能处理简单的流格式的文件
5.测试了若干种载体图片格式:PNG、BMP、ICO、JPG、GIF、JPEG
发现载体图片不兼容JPEG和GIF的格式,兼容性差。

部分代码展示

using 空域替换信息隐写算法.Helper;
using 空域替换信息隐写算法.StegoLogic;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using 空域替换信息隐写算法.Exceptions;
using 空域替换信息隐写算法.Encryption;
using 空域替换信息隐写算法.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>
		//通过完整信息计算汉明距离(没有密码的)
		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
		//通过带有密码的完整信息计算汉明距离
		#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>
		//将消息嵌入图片中(没有密码的)
		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");
			//获得图片的像素比特数
			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)
			{
				// 计算像素位置
				currentPixelX = (int)(currentPixel % carrierWidth);
				currentPixelY = (int)(currentPixel / carrierWidth);
				pixel = stegoImage.GetPixel(currentPixelX, currentPixelY);

				// 在载体通道的位依次嵌入比特信息
				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;
		}
		//将带有密码的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>
		//通过密码和图片导出Message
		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>
		//通过图片导出Message(没有密码的)
		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>
		//将Message转换成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>
		//将信息位嵌入到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>
		//将byte对象的最后一位输出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>
		//将byte对象最后一位输出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>

		//将图片信息转化成string字符串形式
		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
	}
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值