BMP图像信息隐藏
图像隐写算法LSB—Least Significant Bits,又称最不显著位。LSB算法就是将秘密信息嵌入到载体图像像素值得最低有效位,改变这一位置对载体图像的品质影响最小。
原理如下:
以实验用的24位真彩图为例,每个像素用3Byte表示,每Byte分别表示R、G、B三色的亮度,亮度取值范围位0~0xFF。采用LSB算法就是将图像信息的每一Byte的最后一位二进制替换为待嵌入的秘密信息的一位,按顺序进行。因为对最后一位的替换操作其实就是对亮度信息的加一或减一,对颜色影响甚微,所以肉眼难以察觉,这就达到了隐藏信息的目的。对于24位真彩图,所能嵌入的最大秘密信息的大小为像素数量的3/8字节
步骤如下:
- 以二进制的方式读取载体图像并分别头数据与像素数据
- 用二进制秘密信息中的每一笔特信息替换与之对应的载体数据的最低有效位
- 利用得到的新的二进制数据构造图像,即得到含有秘密信息的隐秘图像
/*** change.c ***/ #include<stdio.h> #include<stdlib.h> #include"define.h" int main() { //创建头文件,信息头结构变量 BMP_FILE_HEADER fileHeader; BMP_INFO_HEADER infoHeader; //打开载体图像文件,新建修改后的图像文件 FILE *file = fopen("football.bmp","rb"); FILE *newFile = fopen("change.bmp","wbx"); //读取文件头,信息头 fread(&fileHeader,14,1,file); fread(&infoHeader,40,1,file); //读取24位真彩图像的像素信息 RGB *img = (RGB *)malloc(infoHeader.sizeImage); fread(img,infoHeader.sizeImage,1,file); printf("Picture Size(width x height):%d x %d \n", infoHeader.width, infoHeader.height); //对图片内容进行修改,每隔五个像素染黑一个像素 int i = 0; for (i = 0; i < infoHeader.sizeImage / 3; i += 5) { img[i].red = 0; img[i].green = 0; img[i].blue = 0; } printf("fwirte\n"); //将新的二进制文件写入到新文件中 fwrite(&fileHeader,14,1,newFile); fwrite(&infoHeader,40,1,newFile); fwrite(img,infoHeader.sizeImage,1,newFile); fclose(file); fclose(newFile); return 0; }
修改图像内容的核心代码如下:
int i = 0; for (i = 0; i < infoHeader.sizeImage / 3; i += 5) { img[i].red = 0; img[i].green = 0; img[i].blue = 0; }
infoHeader.sizeImage表示图像数据的字节数,在24位真彩位图中,表示每个像素需要三个字节,infoHeader.sizeImage/3表示位图总像素数
循环体中将每个颜色分量的亮度赋值为0,让像素变黑,最终得到修改后的图像。
以上是处理BMP图像信息的原理。
下面通过LSB算法隐藏秘密信息,这里把define.h文件作为秘密信息隐藏,也可以选择任何大小合适的文件作为秘密信息。
/*** define.c ***/ typedef unsigned short WORD; //2 byte typedef unsigned int DWORD; //4 byte typedef unsigned char BYTE; //1 byte //head of file typedef struct BMP_FILE_HEADER { WORD type; DWORD size; WORD reserved1; WORD reserved2; DWORD offBits; }BMP_FILE_HEADER; //head of infomation typedef struct BMP_INFO_HEADER { DWORD size; int width; int height; WORD planes; WORD bitCount; DWORD compression; DWORD sizeImage; int xPelsPerMeter; int yPelsPerMeter; DWORD colorUsed; DWORD colorImportant; }BMP_INFO_HEADER; //RGBQUAD typedef struct RGBQUAD { BYTE blue; BYTE green; BYTE red; BYTE reserved; }RGBQUAD; //RGB typedef struct RGB { BYTE blue; BYTE green; BYTE red; }RGB;
隐藏信息代码:
/*** hide.c ***/ #include<stdio.h> #include<stdlib.h> #include<sys/stat.h> #include<unistd.h> #include"define.h" //获取文件大小 int getFileSizeSystemCall(char *strFileName) { struct stat temp; stat(strFileName,&temp); return temp.st_size; } int main() { //创建文件头,信息头结构体变量 BMP_FILE_HEADER fileHeader; BMP_INFO_HEADER infoHeader; //打开载体图像文件,读取文件头和信息头,打开隐秘图像信息 FILE *file = fopen("football.bmp","rb"); FILE *newFile = fopen("hide.bmp","wbx"); fread(&fileHeader,14,1,file); fread(&infoHeader,40,1,file); //读取秘密信息文件“define.h” int infoSize = getFileSizeSystemCall("define.h"); printf("info size : %d\n",infoSize); BYTE *info = (BYTE *)malloc(infoSize); FILE *infoFile = fopen("define.h","rb"); fread(info,infoSize,1,infoFile); //读取24位真彩图像像素信息 BYTE *img = (BYTE *)malloc(infoHeader.sizeImage); fread(img,infoHeader.sizeImage,1,file); printf("Picture Size (Width x height) : %d x %d\n",infoHeader.width,infoHeader.height); printf("Can hide %d byte infomation\n",infoHeader.sizeImage/8); //LBS算法实现,把隐秘信息的每一个字节8bit按照低字节到高字节的顺序隐藏到像素信息的 //每8byte中的最低位 int i = 0,j = 1; BYTE tmp = 0x00; for(i = 0; i < infoSize; i++) { for(j = 0; j < 8; j++) { tmp = info[i] &0x01; if(tmp) { img[i*8+j] = img[i * 8 + j] | 0x01; } else { img[i*8+j] = img[i * 8 + j] & 0xfe; } info[i] = info[i] >> 1; } } //将修改后的二进制数据写入到隐秘图像文件中 fwrite(&fileHeader,14,1,newFile); fwrite(&infoHeader,40,1,newFile); fwrite(img,infoHeader.sizeImage,1,newFile); fclose(file); fclose(newFile); return 0; }
LSB算法实现:
int i = 0,j = 1; BYTE tmp = 0x00; for(i = 0; i < infoSize; i++) { for(j = 0; j < 8; j++) { tmp = info[i] &0x01; if(tmp) { img[i*8+j] = img[i * 8 + j] | 0x01; } else { img[i*8+j] = img[i * 8 + j] & 0xfe; } info[i] = info[i] >> 1; } }
info[i]是存储秘密信息的数组,通过把像素Byte数据与0x01按位或运算使得最后一位为1,通过把像素Byte数据与0xfe(1111,1110)做按位与运算使得最后一位为0(0 & x = 0)
stat函数用来获取指定路径下文件或文件夹的属性。路径可以不指定。
使用md5sum命令查看两张图片的MD5 HASH值
再对比一下文件的16进制内容,先使用xxd命令生成16进制内容:
xxd football.bmp > football.hex
xxd hide.bmp > hide.hex
再使用sed命令查看对比第五行内容
sed -n 5p football.hex
sed -n 5p hide.hex
很明显的可以看到像素数据的每一字节的最后一位发生了变化,验证了LSB算法的原理。
提取信息
/*** extract.c ***/ #include<stdio.h> #include<stdlib.h> #include"define.h" int main() { //创建头文件,信息头结构体变量 BMP_FILE_HEADER fileHeader; BMP_INFO_HEADER infoHeader; //读取隐秘图像文件,创建秘密信息文件 FILE *file = fopen("hide.bmp","rb"); FILE *extractFile = fopen("extract.txt","wbx"); BYTE *info = (BYTE *)malloc(738); //读取头文件,信息头 fread(&fileHeader,14,1,file); fread(&infoHeader,40,1,file); //读取24位真彩图像的像素信息 BYTE *img = (BYTE *)malloc(infoHeader.sizeImage); fread(img,infoHeader.sizeImage,1,file); printf("Picture size : %d x %d \n",infoHeader.width,infoHeader.height); //信息提取部分,根据秘密信息的长度,依次读取隐秘图像信息像素信息的最低bit,凭借成Byte int i = 0,j = 0; BYTE tmp = 0x00,ttmp = 0x00; for(i = 0; i < 738; i++) { tmp = 0x00; for(j = 0; j < 8; j++) { /*取每8位bit像素信息的最后一位拼接为1Byte的秘密信息*/ ttmp = img[i*8+j] & 0x01; ttmp = ttmp << j; //左移j位 tmp += ttmp; //每一位累加得到1Byte的tmp值 } info[i] = tmp; } //将提取的信息写入到秘密文件中 fwrite(info,738,1,extractFile); fclose(file); fclose(extractFile); return 0; }
使用md5sum查看两个文件是否相同
md5sum define.h extract.txt
提取过程就是隐藏过程的逆过程,实现原理和隐藏过程类似。需要注意的是,此处使用的文件长度是固定的秘密信息的长度,并且提前知道了隐藏信息包含在了指定图片中。在实际处理中,在隐藏秘密信息时,往往还需要一个嵌入标识和长度信息,来帮助程序判断图片中是否包含秘密信息,并说明秘密信息长度
拓展
修改 hide.c 并使用手动输入的数字作为密钥来规定秘密信息隐藏与载体图片的起始位置。
修改 extract.c 并根据输入的秘钥提取秘密信息
每 Byte 像素信息隐藏 2bit 的秘密信息(MLSB 算法)