信息隐藏技术利用了人类感觉器官对数字信号的感觉冗余,将待隐信息(类似噪声)隐藏在载体文件(如图像、视频、声音)中,使得人的感官(眼、耳)无法觉察到载体文件外部表现上的变化,从而实现隐蔽通信。将文件隐藏于BMP文件中是最简单的隐藏信息的方式。
LSB(Least Significant Bit Embedding)算法即最低有效位嵌入算法。改变每个RGB字节的最低有效位,将其作为容器,将需要隐藏的信息按位分离,放入最低有效位中。插入了信息后的图片仍是一副完整的图片,与原图片在肉眼中区分不出来,达到隐藏信息的效果。本文编程实现了LSB算法,隐藏一个RAR文件至一个BMP文件中,并实现了对应的提取方法。
首先,我们需要了解BMP文件的组成结构。BMP文件由四部分组成:位图文件头、位图信息头、调色板数据和实际位图数据。其中前三部分占据了BMP文件的前54个字节,也就是说LSB算法是保持原BMP文件的前54位不动,而对BMP文件的“实际位图数据”部分进行改动。
然后,我们开始想办法用C语言来实现LSB算法。
在向原始BMP图片中隐藏文件之前,我设计了一个函数来判断原始bmp图片是否能容纳待隐藏文件,若能,返回1;否则,返回0。这个函数的代码如下:
//判断原始bmp图片是否能容纳待隐藏文件,若能,返回1;否则,返回0
int containOK(FILE *pic, FILE *file, long *picLen, long *fileLen) {
//取bmp图片文件长度
fseek(pic, 0, SEEK_END); //将文件指针置尾
*picLen = ftell(pic); //取文件指针当前位置
rewind(pic); //将文件指针重指向文件头
//取待隐藏文件长度
fseek(file, 0, SEEK_END); //将文件指针置尾
*fileLen = ftell(file); //取文件指针当前位置
rewind(file); //将文件指针重指向文件头
if((*fileLen) * 8 > *picLen)
return 0;
else //当位图文件大于待隐藏文件的8倍时,可以容纳
return 1;
}
然后,我开始设计LSB嵌入:首先把要隐藏的文件的信息进行按位分离,再把原始位图文件的“实际位图数据”部分的每一个字节的末位都进行清零操作,然后将已经按位分离的待隐藏文件数据逐个附加到原始位图文件“实际位图数据”部分的每一个字节的末位。具体的函数实现如下:
//LSB嵌入
void encrypt(char *inPic, char *outPic, char *file) {
FILE *in; //指向原始位图文件的指针
FILE *out; //指向新位图文件的指针
FILE *fil; //指向待隐藏文件的指针
long picLen; //原始位图文件的长度
long fileLen; //待隐藏文件的长度
if ((in = fopen(inPic,"rb")) == NULL) {
printf("Cannot open the original bitmap file!\n");
exit(0);
}
if ((out = fopen(outPic,"wb")) == NULL) {
printf("Cannot open the output bitmap file!\n");
exit(0);
}
if ((fil = fopen(file,"rb")) == NULL) {
printf("Cannot open the file to be hidden!\n");
exit(0);
}
if (!containOK(in, fil, &picLen, &fileLen)) {
printf("The file is too large!\n");
exit(0);
}
//储存待隐藏文件信息
int i, j;
char ch;
char *temp = (char *)malloc(8 * fileLen);
i = 0;
while (!feof(fil)) { //将待隐藏文件按位存放于temp数组中
ch = fgetc(fil);
for(j = 0; j < 8; j++)
temp[i++] = 0x01 & (ch >> j);
}
j = 0;
for(i = 1; !feof(in); i++) {
if (i <= 54)
fputc(fgetc(in), out); //原位图文件前54个字节保持不变
else {
if (j < fileLen * 8) //将待隐藏文件数据逐个附加到原位图文件每个字节末位
fputc((fgetc(in) & 0xfe) + temp[j++], out);
else
fputc(fgetc(in) & 0xfe, out);
}
}
//关闭文件
fclose(in);
fclose(out);
fclose(fil);
}
LSB提取,就是将位图文件第54个字节之后的每个字节末位信息提取出来,每8个组成一个字符,再输出到文件中,具体实现如下:
//LSB提取
void decrypt(char *inPic, char *outFile) {
FILE *in; //指向位图文件的指针
FILE *out; //指向提取出的文件的指针
if((in = fopen(inPic,"rb")) == NULL) {
printf("Cannot open the bitmap file!\n");
exit(0);
}
if((out = fopen(outFile,"wb")) == NULL) {
printf("Cannot open the output file!\n");
exit(0);
}
fseek(in, 54L, 0); //将文件指针指向距文件头54字节处
char ch;
do {
ch = 0;
int j;
for( j = 0; j < 8; j++) {
ch += (fgetc(in) & 0x01) << j; //把每8个字节的末位信息组成一个字符
}
fputc(ch, out); //输出
} while (!feof(in));
//关闭文件
fclose(in);
fclose(out);
}
至此,LSB算法实现已经大致完成。完整源代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
//判断原始bmp图片是否能容纳待隐藏文件,若能,返回1;否则,返回0
int containOK(FILE *pic, FILE *file, long *picLen, long *fileLen) {
//取bmp图片文件长度
fseek(pic, 0, SEEK_END); //将文件指针置尾
*picLen = ftell(pic); //取文件指针当前位置
rewind(pic); //将文件指针重指向文件头
//取待隐藏文件长度
fseek(file, 0, SEEK_END); //将文件指针置尾
*fileLen = ftell(file); //取文件指针当前位置
rewind(file); //将文件指针重指向文件头
if((*fileLen) * 8 > *picLen)
return 0;
else //当位图文件大于待隐藏文件的8倍时,可以容纳
return 1;
}
//LSB嵌入
void encrypt(char *inPic, char *outPic, char *file) {
FILE *in; //指向原始位图文件的指针
FILE *out; //指向新位图文件的指针
FILE *fil; //指向待隐藏文件的指针
long picLen; //原始位图文件的长度
long fileLen; //待隐藏文件的长度
if ((in = fopen(inPic,"rb")) == NULL) {
printf("Cannot open the original bitmap file!\n");
exit(0);
}
if ((out = fopen(outPic,"wb")) == NULL) {
printf("Cannot open the output bitmap file!\n");
exit(0);
}
if ((fil = fopen(file,"rb")) == NULL) {
printf("Cannot open the file to be hidden!\n");
exit(0);
}
if (!containOK(in, fil, &picLen, &fileLen)) {
printf("The file is too large!\n");
exit(0);
}
//储存待隐藏文件信息
int i, j;
char ch;
char *temp = (char *)malloc(8 * fileLen);
i = 0;
while (!feof(fil)) { //将待隐藏文件按位存放于temp数组中
ch = fgetc(fil);
for(j = 0; j < 8; j++)
temp[i++] = 0x01 & (ch >> j);
}
j = 0;
for(i = 1; !feof(in); i++) {
if (i <= 54)
fputc(fgetc(in), out); //原位图文件前54个字节保持不变
else {
if (j < fileLen * 8) //将待隐藏文件数据逐个附加到原位图文件每个字节末位
fputc((fgetc(in) & 0xfe) + temp[j++], out);
else
fputc(fgetc(in) & 0xfe, out);
}
}
//关闭文件
fclose(in);
fclose(out);
fclose(fil);
}
//LSB提取
void decrypt(char *inPic, char *outFile) {
FILE *in; //指向位图文件的指针
FILE *out; //指向提取出的文件的指针
if((in = fopen(inPic,"rb")) == NULL) {
printf("Cannot open the bitmap file!\n");
exit(0);
}
if((out = fopen(outFile,"wb")) == NULL) {
printf("Cannot open the output file!\n");
exit(0);
}
fseek(in, 54L, 0); //将文件指针指向距文件头54字节处
char ch;
do {
ch = 0;
int j;
for( j = 0; j < 8; j++) {
ch += (fgetc(in) & 0x01) << j; //把每8个字节的末位信息组成一个字符
}
fputc(ch, out); //输出
} while (!feof(in));
//关闭文件
fclose(in);
fclose(out);
}
int main() {
char inPic[100]; //储存原始位图文件名
char outPic[100];//储存生成位图文件名
char file[100]; //储存加密文件名
printf("**********LSB嵌入**********\n");
printf("请输入原始位图文件名:\n");
scanf("%s",inPic);
printf("请输入生成位图文件名:\n");
scanf("%s",outPic);
printf("请输入加密文件名:\n");
scanf("%s",file);
encrypt(inPic, outPic, file);
printf("LSB嵌入完成!\n");
char infile[100]; //储存待解密位图名
char outfile[100]; //储存提取出的文件名
printf("**********LSB提取**********\n");
printf("请输入待解密位图名:\n");
scanf("%s", infile);
printf("请输入提取出保存的文件名:\n");
scanf("%s", outfile);
decrypt(infile, outfile);
printf("LSB提取完成!\n");
return 0;
}
在运行程序lsb.c前,我准备了一个原始位图文件1.bmp和一个待隐藏文件funny.rar,并将它们与程序lsb.c放在同一目录下:
然后,我开始运行我的程序,将funny.rar隐藏到1.bmp中,生成的新位图文件保存为2.bmp。接着对2.bmp进行LSB提取,提取出的文件保存为3.bmp:
打开程序所在目录,可以看到多出了两个文件2.bmp和3.rar:
原始位图文件1.bmp和加密后的位图文件2.bmp对比如下,可以发现肉眼很难发现差异。
隐藏前的funny.rar与提取出的3.rar解压缩对比如下,两个文件的修改日期和大小均一致,查看内容也一致,因此提取成功。