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}種色值。而人類的眼睛可以區分約1000萬種不同的顏色,這就意味着人類眼睛無法區分的顏色還有600多萬。

RGB

圖1

  當僅僅更改顏色分量的最低位時,人類的眼睛不能區分這前後的變化,LSB就是在該位置存放信息。如圖2所示。

LSB

圖2

實現步驟

  1. 將圖像文件中的所有像素點以RGB形式分隔開,並將各個顏色分量轉換成二進制表示
  2. 把每個顏色分量值的最後一位全部設置成0,對圖像得影響非常細微,不會影響圖像的顯示格式
  3. 信息嵌入:將水印字符轉化爲二進制字符串,並將這些信息依次填入顏色分量的最低位上,即可完成信息的嵌入
  4. 信息提取:將圖像像素的最低位依次提取出來,並進行拼接,即可得到原始信息

程序設計

嵌入信息框圖

LSB-Embed

提取信息框圖

LSB-Extract

文檔結構

文檔結構

程序主界面

主界面

主界面代碼:

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

注意點

  1. 流每次讀取一次,位置就向後推進一定的位數,但可以使用Position屬性設置開始讀取的位置
  2. MemoryStream對象的ReadByte()方法將返回的字節強制轉換成Int32類型

結果展示

嵌入信息

嵌入信息

提取信息

提取信息

附錄

參考鏈接

數字水印

LSB圖片隱寫

LSB隱寫算法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章