图像信息隐藏,通俗地讲,就是将秘密藏进普通的图片文件或者音频文件中。图片文件是由一系列(比方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; }