圖像隱寫算法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 算法)