关于BMP格式文件LSB信息隐藏算法

图像信息隐藏,通俗地讲,就是将秘密藏进普通的图片文件或者音频文件中。图片文件是由一系列(比方10000个)字节组成,秘密信息(中文,英文等)由少量(比方30个汉字,30*2*8=480比特)的字节组成,把图片中每10个字节的最后1位用秘密信息的1位来代替(事先商量好的),就把30个汉字信息藏进普通的图片文件中480个字节中,照片质量基本不受影响。

BMP格式:BMP(位图格式)是DOS和WindowS兼容计算机系统的标准Windows图像格式。BMP格式支持RGB、索引颜色、灰度和位图颜色模式,但不支持Alpha通道。BMP格式支持1、4、24、32位的RGB位图。

典型的BMP图像文件由四部分组成:

1:位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;

2:位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;

3:调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;

4:位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。

信息的隐藏和提取是两个独立部分,分为把信息编码写入图片和把信息从图片提取后解码出来两部分。

LSB 算法设计思路:将待隐藏数据信息以二进制读方式存入buffer,并将一个bitu数据存入一个字节数据的最低位,即将一个字节数据隐藏于8字节数据每字节最低位,由于仅对每一个字节最低位进行修改,图片以肉眼区分无法发现明显失真,隐藏较为隐蔽。

实现方法见代码

bmp_hide:

#include<windows.h>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include<ctime>
#include<iomanip>
#include<sstream>
#include<stack>
#include<vector>
#include <iostream>
#define LENGTH_NAME_BMP 30//bmp图片文件名的最大长度
using namespace std;
//变量定义
BITMAPFILEHEADER strHead;
BITMAPINFOHEADER strInfo;
typedef struct tagIMAGEDATA
{
    //图像数据结构,一个像素
    BYTE blue;//
    BYTE green;//绿
    BYTE red;//
} IMAGEDATA;

void showBmpHead(BITMAPFILEHEADER pBmpHead)
{
    /***function:显示BMP图片文件头**/
    cout<<"位图文件头:";
    (pBmpHead.bfType==0x4d42)?(cout<<"BM"<<endl):(cout<<endl);
    cout<<"文件大小:"<<pBmpHead.bfSize<<"字节"<<endl;
    cout<<"保留字_1:"<<pBmpHead.bfReserved1<<endl;
    cout<<"保留字_2:"<<pBmpHead.bfReserved2<<endl;
    cout<<"实际位图数据的偏移字节数:"<<pBmpHead.bfOffBits<<"字节"<<endl<<endl;
}
void showBmpInforHead(tagBITMAPINFOHEADER pBmpInforHead)
{
    /***function:显示BMP图片信息头**/
    cout<<"位图信息头:"<<endl;
    cout<<"结构体的长度:"<<pBmpInforHead.biSize<<endl;
    cout<<"位图宽:"<<pBmpInforHead.biWidth<<endl;
    cout<<"位图高:"<<pBmpInforHead.biHeight<<endl;
    cout<<"平面数:"<<pBmpInforHead.biPlanes<<endl;
    cout<<"采用颜色位数:"<<pBmpInforHead.biBitCount<<endl;
    cout<<"压缩方式:"<<pBmpInforHead.biCompression<<endl;
    cout<<"实际位图数据占用的字节数:"<<pBmpInforHead.biSizeImage<<endl;
    cout<<"X方向分辨率:"<<pBmpInforHead.biXPelsPerMeter<<endl;
    cout<<"Y方向分辨率:"<<pBmpInforHead.biYPelsPerMeter<<endl;
    cout<<"使用的颜色数:"<<pBmpInforHead.biClrUsed<<endl;
    cout<<"重要颜色数:"<<pBmpInforHead.biClrImportant<<endl;
}

void ConverseBinary(const char* ch,int length,vector<int> &val)
{
    /**
    function:把字符串的ASCII码转为二进制
    ch:输入字符串
    val:字符的二进制数组,
    **/
    stack<int> stk;
    for(int i = 0; i<length; i++)
    {
        int ascii = ch[i];/**取字符**/
        for(int j = 0; j<8; j++)/**转为二进制 **/
        {
            stk.push(ascii%2);
            ascii>>=1;
        }
        for(int k = 0; k<8; k++)/**出栈,存入数组 **/
        {
            val.push_back(stk.top());
            stk.pop();
        }
    }
}

void HideMessage(IMAGEDATA* &imagedata,vector<int> &val)
{
    for(int i = 0,j=0; i < val.size(); j++)
    {
        imagedata[j].blue|=1;/**或1最低位置1**/
        if(val[i++]==0)
            imagedata[j].blue^=1;/**异或1最低位置0**/
        imagedata[j].green|=1;
        if(val[i++]==0)
            imagedata[j].green^=1;
        imagedata[j].red|=1;
        if(val[i++]==0)
            imagedata[j].red^=1;
    }
}

int main()
{
    char strFile[LENGTH_NAME_BMP] ;     //bmp文件名
    IMAGEDATA *imagedata = NULL;                    //动态分配存储原图片的像素信息的二维数组
    int width,height;   //图片的宽度和高度

    cout<<"请输入隐藏信息载体BMP图片路径名:"<<endl;
    cin>>strFile;//输入文件名

    FILE *fpi;                                      //读取图片内容

    FILE *fpw;                                      //写入文件内容

    fpi=fopen(strFile,"rb");                        //只读方式打开文件

    /**
    //FILE * fopen(const char * path, const char * mode);
    //返回值:文件顺利打开后,指向该流的文件指针就会被返回。
    //如果文件打开失败则返回 NULL,并把错误代码存在 error 中。
    **/

    if(fpi != NULL)
    {
        fread(&strHead,sizeof(tagBITMAPFILEHEADER),1,fpi);        //读取文件头
        /***
        //函数原型:
        //size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
        //buffer 用于接收数据的内存地址
        //size 要读的每个数据项的字节数,单位是字节
        //count 要读count个数据项,每个数据项size个字节.
        //stream 输入流
        //返回值    返回真实读取的项数,若大于count则意味着产生了错误。
        //另外,产生错误后,文件位置指示器是无法确定的。
        //若其他stream或buffer为空指针,
        //或在unicode模式中写入的字节数为奇数,
        //此函数设置errno为EINVAL以及返回0.
        ***/

        if(0x4d42!=strHead.bfType)
        {
            cout<<"此文件不是bmp格式文件!"<<endl;
            system("pause");
            return 0;
        }

        //showBmpHead(strHead);               //显示文件头
        fread(&strInfo,sizeof(tagBITMAPINFOHEADER),1,fpi);
        //showBmpInforHead(strInfo);              //显示文件信息头

        width = strInfo.biWidth;
        height = strInfo.biHeight;

        imagedata = (IMAGEDATA*)malloc(width * height * sizeof(IMAGEDATA));

        //初始化原始图片的像素数组
        for(int i = 0; i < height; ++i)
        {
            for(int j = 0; j < width; ++j)
            {
                (*(imagedata + i * width + j)).blue = 0xff;
                (*(imagedata + i * width + j)).green = 0xcc;
                (*(imagedata + i *  width + j)).red = 0x66;
            }
        }

        //fseek(fpi,54,SEEK_SET);//定位到54字节后

        fread(imagedata,sizeof(struct tagIMAGEDATA) * width,height,fpi);//读出图片的像素数据
        fclose(fpi);
    }
    else
    {
        cout<<"文件打开错误!"<<endl;
        system("pause");
        return 0;
    }

    /*********图片隐藏信息处理*************/
    int capacity = width*height*3/8;
    string s;
    const char* ch;
    vector<int> val;

    cout<<"文件容量:"<<capacity<<"字节"<<endl;
    cout<<"请输入要隐藏的信息:";
    cin>>s;
    ch = s.data();

    ConverseBinary(ch,s.length(),val);
    HideMessage(imagedata,val);

    /**********************/
    //保存bmp图片
    string fname;
    cout<<"请输入要保存的文件名:"<<endl;
    cin>>fname;
    const char* filename = fname.data();

    //创建图片文件
    if((fpw=fopen(filename,"wb"))==NULL)
    {
        cout<<"创建BMP文件错误!"<<endl;
        system("pause");
        return 0;
    }

    fwrite(&strHead,sizeof(tagBITMAPFILEHEADER),1,fpw);             //写入位图文件头
    /**
    //size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
    //buffer:是一个指针,对fwrite来说,是要获取数据的地址;
    //size:要写入内容的单字节数;
    //count:要进行写入size字节的数据项的个数;
    //stream:目标文件指针;
    //返回值:返回实际写入的数据项个数count。
    **/

    fwrite(&strInfo,sizeof(tagBITMAPINFOHEADER),1,fpw);             //写入位图信息头
    fwrite(imagedata,sizeof(struct tagIMAGEDATA) * width,height,fpw);//写入位图数据
    fclose(fpw);

    cout<<endl<<"信息已隐藏在文件:"<<fname<<endl<<endl;

    delete[] imagedata;//释放内存
    system("pause");
    return 0;
}

生成的bmp图片文件隐藏的信息需要通过对应的解密程序进行提取

bmp_extract:

#include<windows.h>
#include <stdio.h>
#include <cstdlib>
#include <cmath>
#include<ctime>
#include<iomanip>
#include<sstream>
#include<stack>
#include<vector>
#include <iostream>
#define LENGTH_NAME_BMP 30//bmp图片文件名的最大长度
using namespace std;
//变量定义
BITMAPFILEHEADER strHead;
BITMAPINFOHEADER strInfo;
typedef struct tagIMAGEDATA
{//图像数据结构,一个像素
    BYTE blue;//
    BYTE green;//绿
    BYTE red;//
} IMAGEDATA;

void ExtractMessage(IMAGEDATA* &imagedata,int length,vector<int> &extract)
{
    for(int i = 0; i < length*8/3; i++)
    {
        int n = ((int)imagedata[i].blue);
        n&=1;
        extract.push_back(n);

        n = ((int)imagedata[i].green);
        n&=1;
        extract.push_back(n);

        n = ((int)imagedata[i].red);
        n&=1;
        extract.push_back(n);
    }
}

string ConverseString(vector<int> &extract)
{
    string stri;
    for(int j = 0; j<extract.size()/8; j++)
    {
        int buffer = 0;
        for (int k = 0; k<8; k++)
        {
            buffer<<=1;
            buffer+=extract[j*8+k];
        }
        stri+= buffer;
    }
    return stri;
}

int main()
{
    char strFile[LENGTH_NAME_BMP];
    IMAGEDATA *imagedata = NULL;
    int width,height;
    cout<<"请输入已隐藏信息的载体BMP文件路径名:";
    cin>>strFile;
    FILE *fpi;
    fpi=fopen(strFile,"rb");
    if(fpi != NULL)
    {
        fread(&strHead,sizeof(tagBITMAPFILEHEADER),1,fpi);
        if(0x4d42!=strHead.bfType)
        {
            cout<<"此文件不是bmp格式文件!"<<endl;
            system("pause");
            return 0;
        }
        fread(&strInfo,sizeof(tagBITMAPINFOHEADER),1,fpi);
        width = strInfo.biWidth;
        height = strInfo.biHeight;
        imagedata = (IMAGEDATA*)malloc(width * height * sizeof(IMAGEDATA));
        for(int i = 0; i < height; ++i)
        {
            for(int j = 0; j < width; ++j)
            {
                (*(imagedata + i * width + j)).blue = 0xff;
                (*(imagedata + i * width + j)).green = 0xcc;
                (*(imagedata + i *  width + j)).red = 0x66;
            }
        }
        fread(imagedata,sizeof(struct tagIMAGEDATA) * width,height,fpi);
        fclose(fpi);
    }
    else
    {
        cout<<"文件打开错误!"<<endl;
        system("pause");
        return 0;
    }
    /**********图片提取信息处理************/
    int capacity = width*height*3/8;
    int messageLength;
    cout<<"此文件可容纳信息量:"<<capacity<<"字节"<<endl;

    vector<int> extract;

    //cout<<endl<<"文件容量:"<<capacity<<"字节"<<endl;
    cout<<"要提取的信息长度:";
    cin>>messageLength;

    ExtractMessage(imagedata,messageLength,extract);
    string stri = ConverseString(extract);

    cout<<"图片内隐藏的信息为:"<<endl<<stri<<endl;
    /**********************/

    //释放内存
    delete[] imagedata;
    system("pause");
    return 0;
}

 

posted @ 2019-05-23 23:38  Mydrizzle  阅读(1370)  评论(0)    收藏  举报