算法原理
数字水印(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
}
}