LSB算法分析与实现
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,既有2242^{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){// 判断类型表示字节是否为2if (imgFormat.Length == 2){// 判断标识字节是否为:42 4dif (imgFormat[0] == 0x42 && imgFormat[1] == 0x4d){// 是Bitmapreturn 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的空域信息隐藏实现
目录 实验环境 系统设计 BMP文件格式 LSB空域信息隐藏算法 嵌入算法 提取算法 LSB空域信息隐藏算法分析 优点 缺点 系统实现 HidenMsg.txt 信息隐藏:HideMsg.m 信息提取 ...
- 基于FCN,U-Net的深度学习医学影像分割算法(细胞分割算法)以及传统算法分析
本博文主要分析了基于传统计算机视觉的细胞分割算法和基于深度学习的细胞分割算法.主要针对医学影像分割算法.包括了FCN全卷积网络的细胞分割效果,U-Net细胞分割效果.查阅了采用深度学习分割医学影像的几 ...
- Weiss的数据结构与算法分析(C++版)源码编译说明
最近尝试编译Weiss的那本数据结构与算法分析(C++版)提供的源代码时,遇到一些问题,特记录如下: 考虑到该书提供的代码是使用模板技术较多,这在提供简洁代码的同时,也给源码的编译带来了一些问题.因而 ...
- 反汇编算法介绍和应用——递归下降算法分析
上一篇博文我介绍了Windbg使用的线性扫描(linear sweep)反汇编算法.本文我将介绍IDA使用的递归下降(recursive descent)反汇编算法.(转载请指明来源于breaksof ...
- LSB图像信息隐藏算法matlab,实验二LSB信息隐藏实验.doc
实验二LSB信息隐藏实验.doc 实验二LSB信息隐藏实验 综合评分: [实验目的]: 掌握MATLAB基木操作 实现LSB信息隐藏和提取 [实验内容]:(请将你实验完成的项11涂"■&qu ...
- 数据结构与算法分析(C++版)(第二版)
查看书籍详细信息: 数据结构与算法分析(C++版)(第二版) 内容简介 本书采用程序员最爱用的面向对象C++语言来描述数据结构和算法,并把数据结构原理和算法分析技术有机地结合在一起,系统介绍了各种类型 ...
- 总结:DCIC算法分析赛完整方案分享!
↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:阿水,北航计算机硕士,Datawhale成员 DCIC2020 本 ...
- 算法分析赛:从数据中挖掘价值,72万奖金,DCIC 2020 大数据赛道来了!
01 DCIC 2020:数字中国创新大赛 DCIC 是国内赛事中少有的开放政府真实数据的经典赛事,除了高知名度,高影响力,高奖金,对能力履历,学术研究和企业技术应用都提供了很好的机会.作为数字中国 ...
- 线性排序算法分析总结
线性排序(Linear sort),指的是 时间复杂度为 O(n) 的排序算法.之所以时间复杂度能达到线性,是因为这种排序不是基于比较的,但它的适用场景也有很大的局限性. 线性排序有三种:桶排序.计数 ...
- python数据结构与算法分析_数据结构与算法(Python版)
为什么研究数据结构与算法 本周带大家进入Python版数据结构与算法的学习.想必大家都听过"算法"一词,算法的学习对编程者来说是至关重要的.首先我们先了解一下为什么要研究数据结构与 ...
最新文章
- SpringBoot学习之@Configuration注解和@Bean注解
- ubuntu系统中samba服务器搭建
- Shell脚本基本命令3
- java虚拟机工作原理图_Java虚拟机工作原理
- 杭电1108java_按照这个步骤来刷题,迷茫的你两个月亦能成为王者
- php 芝麻认证think_PHP 芝麻信用接入的注意事项
- Linix中Dockerfile部署Spring Boot项目
- [转载] Python reversed函数及用法【小白学习Python必备知识】
- 线程池创建线程数量讨论
- 最全原理图元器件查询
- 沉默,并不代表我们无话可说
- 作业五:团队项目——项目启动及需求分析
- 京东手机电商大数据统计平台搭建
- Spring Boot进阶(13):如何获取@ApiModelProperty(value = “序列号“, name = “uuid“)中的value值 | 超级详细,建议收藏
- DB、DBS和DBMS关系
- Python高级编程之数据库sqlite3(一)
- nodejs下载及安装(windows)
- 如何用Python求众数
- 清除windows系统垃圾文件简易脚本(bat)
- 计算机二级试题第12套,计算机等级考试二级VFP机试试题12