概述
Title:《LSB算法分析及实现》
Author: Hugu
Started Date: Oct. 10th. 2019.
Finished Date: Oct. 11th. 2019.
数字水印
数字水印(Digital Watermark)是一种应用计算机算法嵌入载体文件的保护信息,数字水印技术是一种基于内容的、非密码机制的计算机信息隐藏技术,它是将一些标识信息(即数字水印)直接嵌入数字载体当中(包括多媒体、文档、软件等)或是间接表示(修改特定区域的结构),且不影响原载体的使用价值,也不容易被探知和再次修改,但可以被生产方识别和辨认。
常用的数字水印有很多种,LSB是其中一种简单的隐写算法。
LSB简介
LSB全称为 Least Significant Bit(最低有效位),是一种常被用做图片隐写的算法(在CTF中经常见到她的身影)。LSB属于空域算法中的一种,是将信息嵌入到图像点中像素位的最低位,以保证嵌入的信息是不可见的,但是由于使用了图像不重要的像素位,算法的鲁棒性差,水印信息很容易为滤波、图像量化、几何变形的操作破坏。
算法分析
PNG和BMP图片中的图像像素一般是有由RGB三原色组成(如图1所示),每一种颜色占用8位,取值范围为0x00~0xFF,既有 2 24 2^{24} 224种色值。而人类的眼睛可以区分约1000万种不同的颜色,这就意味着人类眼睛无法区分的颜色还有600多万。
当仅仅更改颜色分量的最低位时,人类的眼睛不能区分这前后的变化,LSB就是在该位置存放信息。如图2所示。
实现步骤
- 将图像文件中的所有像素点以RGB形式分隔开,并将各个颜色分量转换成二进制表示
- 把每个颜色分量值的最后一位全部设置成0,对图像得影响非常细微,不会影响图像的显示格式
- 信息嵌入:将水印字符转化为二进制字符串,并将这些信息依次填入颜色分量的最低位上,即可完成信息的嵌入
- 信息提取:将图像像素的最低位依次提取出来,并进行拼接,即可得到原始信息
程序设计
嵌入信息框图:
提取信息框图:
文档结构:
程序主界面:
主界面代码:
using System;
using System.Windows.Forms;
namespace LSBAlgorithmDemo
{
public partial class MainFrm : Form
{
#region 定义全局变量
Form frm;
#endregion
#region 构造函数
public MainFrm()
{
InitializeComponent();
}
#endregion
#region 嵌入按钮得Click事件
private void btnEnbed_Click(object sender, EventArgs e)
{
frm = new InfoEmbedmentFrm();
this.Hide();
if(frm.ShowDialog() == DialogResult.Cancel)
{
this.Show();
}
else
{
Application.Exit();
}
}
#endregion
#region 提取按钮的Click事件
private void btnExtract_Click(object sender, EventArgs e)
{
frm = new InfoExtractionFrm();
this.Hide();
if (frm.ShowDialog() == DialogResult.Cancel)
{
this.Show();
}
else
{
Application.Exit();
}
}
#endregion
}
}
嵌入界面:
嵌入界面代码:
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
namespace LSBAlgorithmDemo
{
public partial class InfoEmbedmentFrm : Form
{
#region 定义全局变量
private string fileName;
private LSBHelper lsb;
private string msg = null;
private MemoryStream memStream = null;
#endregion
#region 构造函数
public InfoEmbedmentFrm()
{
InitializeComponent();
// 初始化界面
Form_Init(0);
}
#endregion
#region 设置显示界面
/// <summary>
/// 设置显示界面
/// </summary>
/// <param name="flag">0:初始化、下载之后;1:图片加载之后;2:图片嵌入之后
/// </param>
private void Form_Init(int flag)
{
if(flag == 0)//初始化、下载之后
{
// 设置界面显示的起始位置
this.StartPosition = FormStartPosition.CenterScreen;
// PictureBox控件
pBoxDisplay.SizeMode = PictureBoxSizeMode.StretchImage;
// 两个必须都要设置(因两次的加载途径不一样)
pBoxDisplay.ImageLocation = null;
pBoxDisplay.Image = null;
// RichTextBox控件
rtbInfo.Clear();
rtbInfo.ReadOnly = true;
// Button控件
btnDownloadImage.Enabled = false;
btnEmbedInfo.Enabled = false;
btnLoadImage.Enabled = true;
btnLoadImage.Focus();
}
else if(flag == 1)//图片加载之后
{
// RichTextBox控件
rtbInfo.Clear();
rtbInfo.ReadOnly = false;
rtbInfo.Focus();
// Button控件
btnDownloadImage.Enabled = false;
btnEmbedInfo.Enabled = true;
btnLoadImage.Enabled = true;
}
else if(flag == 2)//图片嵌入之后
{
// Button控件
btnDownloadImage.Enabled = true;
btnEmbedInfo.Enabled = true;
btnLoadImage.Enabled = true;
btnDownloadImage.Focus();
}
}
#endregion
#region 加载图片的事件
private void btnLoadImage_Click(object sender, EventArgs e)
{
// 再次单击加载图片按钮时,回到初始化状态
Form_Init(0);
// 设置打开对话框的标题
openImageDialog.Title = "请选择一张位图";
// 对文件格式进行筛选
openImageDialog.Filter = "bmp | *.bmp;*.BMP";
// 默认设置为空
openImageDialog.FileName = "";
if (openImageDialog.ShowDialog() == DialogResult.OK)
{
// 存储打开文件的全文件名
fileName = openImageDialog.FileName;
// 设置显示图片的样式
pBoxDisplay.SizeMode = PictureBoxSizeMode.StretchImage;
// 加载要显示的图片
pBoxDisplay.ImageLocation = fileName;
// 设置显示界面
Form_Init(1);
}
}
#endregion
#region 嵌入文本的事件
private void btnEmbedInfo_Click(object sender, EventArgs e)
{
// 已经在界面设置中代替
if (string.IsNullOrWhiteSpace(fileName))
{
MessageBox.Show("请选择一张位图", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else if (string.IsNullOrWhiteSpace(rtbInfo.Text))
{
MessageBox.Show("请输入要嵌入的文本信息", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
lsb = new LSBHelper();
if (lsb.EmbedInfo(fileName, rtbInfo.Text, out msg, out memStream))
{
pBoxDisplay.ImageLocation = null;
//pBoxDisplay.ImageLocation = "C:\Users\ss\Desktop\664204.bmp";
pBoxDisplay.Image = Image.FromStream(memStream);
MessageBox.Show(msg, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
// 设置显示界面
Form_Init(2);
}
else
{
MessageBox.Show(msg, "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
#endregion
#region 下载图片按钮触发的事件
private void btnDownloadImage_Click(object sender, EventArgs e)
{
if (memStream != null)
{
saveImageDialog.Filter = "位图(*.bmp)|*.bmp";
if (saveImageDialog.ShowDialog() == DialogResult.OK)
{
FileStream fs = null;
BinaryWriter bw = null;
try
{
fs = new FileStream(saveImageDialog.FileName, FileMode.Create, FileAccess.Write);
bw = new BinaryWriter(fs);
// 指针回位(每次操作之前都要先操作一下)
memStream.Position = 0;
while (memStream.Position != memStream.Length)
{
// ReadByte()强制转换成Int32类型
byte write = Convert.ToByte(memStream.ReadByte());
bw.Write(write);
}
MessageBox.Show("图片下载成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
//设置显示界面
Form_Init(0);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
finally
{
fs.Close();
bw.Close();
}
}
}
}
#endregion
}
}
提取界面:
提取界面代码:
using System;
using System.IO;
using System.Windows.Forms;
namespace LSBAlgorithmDemo
{
public partial class InfoExtractionFrm : Form
{
#region 定义全局变量
private string fileName;
private MemoryStream memorystream = null;
private string info = null;
private string msg = null;
#endregion
#region 构造函数
public InfoExtractionFrm()
{
InitializeComponent();
// 初始化展示界面
Form_Init(0);
}
#endregion
#region 界面设置操作
/// <summary>
/// 界面设置操作
/// </summary>
/// <param name="flag">
/// 0:初始化
/// 1:提取之后
/// </param>
private void Form_Init(int flag)
{
if(flag == 0)
{
// 设置界面显示位置
this.StartPosition = FormStartPosition.CenterScreen;
// 设置PictureBox控件
pBoxDisplay.SizeMode = PictureBoxSizeMode.StretchImage;
pBoxDisplay.ImageLocation = null;
// 设置RichTextBox控件
rtbInfo.Clear();
rtbInfo.ReadOnly = true;
// 设置Button控件
btnLoadImage.Enabled = true;
btnExtractInfo.Enabled = false;
}
else if(flag == 1)
{
// 设置RichTextBox控件
rtbInfo.Clear();
// 设置Button控件
btnLoadImage.Enabled = true;
btnExtractInfo.Enabled = true;
}
}
#endregion
#region 加载图片
private void btnLoadImage_Click(object sender, EventArgs e)
{
Form_Init(0);
// 设置打开对话框的标题
openImageDialog.Title = "请选择一张已嵌入信息的位图";
// 对文件格式进行筛选
openImageDialog.Filter = "bmp | *.bmp;*.BMP";
openImageDialog.FileName = "";
if (openImageDialog.ShowDialog() == DialogResult.OK)
{
// 存储打开文件的全文件名
fileName = openImageDialog.FileName;
// 设置显示图片的样式
pBoxDisplay.SizeMode = PictureBoxSizeMode.StretchImage;
// 加载要显示的图片
pBoxDisplay.ImageLocation = fileName;
Form_Init(1);
}
}
#endregion
#region 提取信息
private void btnExtractInfo_Click(object sender, EventArgs e)
{
// 图片是否加载
if (string.IsNullOrWhiteSpace(fileName))
{
MessageBox.Show("请选嵌入文字的位图", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
else
{
try
{
LSBHelper lsb = new LSBHelper();
// 将图像存入内存流中
using (FileStream fileReadStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
using (BinaryReader br = new BinaryReader(fileReadStream))
{
byte write;
memorystream = new MemoryStream();
for (int i = 1; i <= fileReadStream.Length; i++)
{
write = br.ReadByte();
memorystream.WriteByte(write);
}
// 回位操作
memorystream.Position = 0;
}
}
// 信息提取
if (lsb.ExtractInfo(memorystream, out info, out msg))
{
// 将提取到的信息展示在文本控件中
rtbInfo.Text = info;
}
else
{
MessageBox.Show(msg, "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
finally
{
// 释放占用的资源
memorystream.Dispose();
}
}
}
#endregion
}
}
LSBHelper代码:
using System;
using System.IO;
namespace LSBAlgorithmDemo
{
public class LSBHelper
{
#region 定义全局变量
FileStream fileReadStream = null;
MemoryStream memStream = null;
BinaryReader br = null;
#endregion
#region 用于判断图片是否为Bitmap格式
/// <summary>
/// 用于判断图片是否为Bitmap格式
/// </summary>
/// <param name="imgFormat">图片前几个字节</param>
/// <returns>返回判断结果</returns>
private bool IsBitmap(byte[] imgFormat)
{
// 判断类型表示字节是否为2
if (imgFormat.Length == 2)
{
// 判断标识字节是否为:42 4d
if (imgFormat[0] == 0x42 && imgFormat[1] == 0x4d)
{
// 是Bitmap
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
#endregion
#region 判断文本信息能否写入图片中
/// <summary>
/// 判断文本信息能否写入图片中
/// </summary>
/// <param name="streamSize">图片流的字节个数</param>
/// <param name="infoSize">文本信息的字符个数</param>
/// <returns>返回判断结果</returns>
private bool CanEmbed(long streamSize, long infoSize)
{
if (streamSize < (infoSize * 16 + 54))
{
return false;
}
else
{
return true;
}
}
#endregion
#region 判断指定文件是否存在
/// <summary>
/// 判断指定文件是否存在
/// </summary>
/// <param name="fullFileName">文件全名称</param>
/// <returns>返回判断结果</returns>
private bool IsFileExists(string fullFileName)
{
return File.Exists(fullFileName);
}
#endregion
#region 判断图像中是否写入信息
private bool IsEmabed(MemoryStream memorystream)
{
// 复位
memorystream.Position = 0;
memorystream.Seek(54L, 0);
byte reader;
char ch;
int temp = 0;
int state = 0;
int index = 0;
while((index = memorystream.ReadByte()) > 0 && index != memorystream.Length)
{
temp = 0;
for (int k = 0; k < 16; k++)
{
reader = Convert.ToByte(memorystream.ReadByte());
temp += (reader & 0x01) << k;
}
ch = Convert.ToChar(temp);
if (ch == '#')
state++;
}
if (state == 2)
{
return true;
}
else
{
return false;
}
}
#endregion
#region 将待写入图片的信息转成"# + info + #"的格式并储存在tempChs字符数组中
/// <summary>
/// 将待写入图片的信息转成"# + info + #"的格式并储存在tempChs字符数组中
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
private char[] GetTempChsFromInfo(string info)
{
// 将文本信息转成Unicode字符值并存储在char数组中
char[] infoToChs = new char[info.Length];
for (int i = 0; i < info.Length; i++)
{
infoToChs[i] = Convert.ToChar(info[i]);
}
// 将待写入图片的信息转成指定格式并储存在tempChs字符数组中
char[] tempChs = new char[(info.Length + 2) * 16];
// 前导符“#”:16位
for (int n = 0; n < 16; n++)
{
tempChs[n] = Convert.ToChar(0x0001 & ('#' >> n));
}
// 原主要信息
int k;
for (k = 1; k < infoToChs.Length + 1; k++)
{
for (int n = 0; n < 16; n++)
{
tempChs[16 * k + n] = Convert.ToChar(0x0001 & infoToChs[k - 1] >> n);
}
}
// 后导符“#”:16位
for (int n = 0; n < 16; n++)
{
tempChs[16 * k + n] = Convert.ToChar(0x0001 & '#' >> n);
}
return tempChs;
}
#endregion
#region 向Bitmap中嵌入信息
public bool EmbedInfo(string fullFileName, string info, out string msg, out MemoryStream memory)
{
// 判断指定图片是否存在
if (!IsFileExists(fullFileName))
{
msg = "请确认指定的图片是否存在";
memory = null;
return false;
}
try
{
// 得到图片的字节流读取器
fileReadStream = new FileStream(fullFileName, FileMode.Open, FileAccess.Read);
br = new BinaryReader(fileReadStream);
// 读取图片的标识符
byte[] imgFormat = new byte[2];
imgFormat[0] = br.ReadByte();
imgFormat[1] = br.ReadByte();
br.BaseStream.Position = 0;
// 判断指定图片是否为Bitmap格式
if (!IsBitmap(imgFormat))
{
msg = "请选择Bitmap格式的图片";
memory = null;
return false;
}
// 判断指定图片是否能够写下信息
if (!CanEmbed(fileReadStream.Length, info.Length))
{
msg = "该图片无法写下指定的全部信息,请选择更大尺寸的Bitmap图片";
memory = null;
return false;
}
// 将待写入图片的信息转成指定格式并储存在tempChs字符数组中
char[] tempChs = GetTempChsFromInfo(info);
// 使用LSB算法将整合后的文件流写入内存流中
memStream = new MemoryStream();
byte write;
for (int i = 1, j = 0; i <= fileReadStream.Length; i++)
{
write = br.ReadByte();
// 前54字节不能动
if (i <= 54)
{
memStream.WriteByte(write);
}
else
// 在数据区写入信息
{
if (j < tempChs.Length)
{
memStream.WriteByte(Convert.ToByte((write & 0xfe) + tempChs[j]));
}
else
{
memStream.WriteByte(write);
}
j++;
}
}
memStream.Position = 0;
msg = "信息已成功嵌入图片中";
memory = memStream;
return true;
}
catch (Exception ex)
{
msg = ex.Message;
memory = null;
return false;
}
finally
{
// 执行完毕之后一定要关闭创建的资源
fileReadStream.Close();
//memStream.Close();
br.Close();
}
}
#endregion
#region 提取Bitmap中的信息
public bool ExtractInfo(MemoryStream memorystream, out string info, out string msg)
{
#region 读取图片的标识符并判断文件是否为指定类型
// 读取图片的标识符
byte[] imgFormat = new byte[2];
imgFormat[0] = Convert.ToByte(memorystream.ReadByte());
imgFormat[1] = Convert.ToByte(memorystream.ReadByte());
memorystream.Position = 0;
// 判断指定图片是否为Bitmap格式
if (!IsBitmap(imgFormat))
{
info = null;
msg = "请选择Bitmap格式的图片";
return false;
}
#endregion
#region 提取信息
// 回位
memorystream.Position = 0;
memorystream.Seek(54L, 0);
byte reader;
char ch;
int temp = 0;
int state = 0;
info = "";
while (Convert.ToChar(temp) != '#' || state != 2)
{
temp = 0;
for (int k = 0; k < 16; k++)
{
reader = Convert.ToByte(memorystream.ReadByte());
temp += (reader & 0x01) << k;
}
ch = Convert.ToChar(temp);
if (ch != '#' && state != 1)
{
info = null;
msg = "没有嵌入文字信息或者信息已经被破坏";
return false;
}
if (ch != '#' && state == 1)
{
info += ch.ToString();
}
if (ch == '#')
state++;
}
msg = "成功";
return true;
#endregion
}
#endregion
}
}
注意点:
- 流每次读取一次,位置就向后推进一定的位数,但可以使用Position属性设置开始读取的位置
- MemoryStream对象的ReadByte()方法将返回的字节强制转换成Int32类型
结果展示
嵌入信息:
提取信息:
附录
参考链接:
数字水印
LSB图片隐写
LSB隐写算法
最后
以上就是顺心树叶为你收集整理的LSB算法分析与实现数字水印LSB简介算法分析实现步骤程序设计结果展示附录的全部内容,希望文章能够帮你解决LSB算法分析与实现数字水印LSB简介算法分析实现步骤程序设计结果展示附录所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复