利用24位BMP位图进行加密
郎锐
01-6-29 上午 09:27:42
--------------------------------------------------------------------------------
24位BMP位图文件
要利用BMP位图进行加密首先需要了解BMP文件的存放格式,24位真彩BMP位图文件包括3部分:
第一部分是BMP文件头:前2个字节是“BM”,是用于识别BMP文件的标志;第3、4、5、6字节存放的是位图文件的大小,以字节为单位;第7、8、9、10字节是保留的,必须为0;第11、12、13、14字节给出位图阵列相对于文件头的偏移。
第二部分是点位图 信息 :从第29个字节开始,第29、30字节描述的是像素的位数;第35、36、37、38字节确定图像字节数的多少,但通常此项为空。
第三部分是位图阵列:从第39个字节开始,每3个字节表示一个像素,这3个字节依次表示该像素的红、绿、蓝亮度分量值。要从位图文件中“挤"出用来隐藏其他重要信息的存储空间,就需要从这里入手。
实现原理
根据亮度公式:I=0.3R+0.59G+0.11B可以知道人眼对于绿色分量最为敏感,对蓝色分量最不敏感,绿色分量值改变1个单位就相当于蓝色分量改变5个、红色分量改变2个单位。折算为二进制,红色分量可以改变其低两位、绿色分量可以改变其最低位、蓝色分量可以改变其低三位。这样就可以从描述每个像素的3个字节中得到2+1+3=6个比特的冗余信息位用于存储其他文件的比特流。按此比率,从一幅24位BMP位图中可以得到的最大存储空间为:((位图文件大小-40)×6)/(3×8),大约是其位图文件长度的1/4。
由于位图文件中每3个字节只能提供6个比特的存储空间,所以需要12个字节才能完整保存源文件的3个字节。
本文制定的加密协议是先通过程序窗口输入密码,从位图文件的第39个字节即位图阵列部分开始存储,依次为:源文件名、分割符“*”、源文件的长度、分割符“*”、源文件的内容。其中源文件内容的每个字节需与密码相异或,以起到对文件进一步加密的效果。
这里需要特别指出的是:如果在 网络 传输时该文件被截获,从中将加密文件恢复出来的可能性是非常小的。首先,从外观上加密后的文件和一般的BMP文件没有任何区别,而且加密前后位图文件大小也不会发生变化,不会引起截获者的注意。其次,由于只有大约1/4的空间存有隐含信息,对于被加密信息而言,其他3/4的内容都是冗余位。在密码学上,往往采取加大冗余位的方式来 提高 加密的可靠性,显然这种拥有75%的冗余码的加密方法还是颇为可靠的。最后,破译者并不知晓加密所采取的协议,而且待加密的内容都同加密密钥经过了异或运算,在没有正确解密密码的情况下也是很难破译的。
源文件的加密
由于进行加密处理的过程中要频繁进行位运算,所以选用在这方面较有优势的Micorsoft Visual C++ 6.0作为开发工具。
加密的关键是将待加密信息组成比特流,并将其按规定的格式依次填入载体位图文件的指定位中。首先要做好准备工作,由于每12个位图文件的字节恰好可以用于保存3个源文件的字节,而12又是3的整数倍,所以可以设定每12个字节为一个循环。下面5个数组中存放的数据用于处理每个循环中的各个字节所要进行的移位量和掩码值等:
//填冗余位时的移位序列
int move1[13]={6,5,2,0,7,4,2,1,-2,6,4,3,0};
//源文件的字节掩码序列
unsigned char mask1[13]={192,32,28,3,128,112,12,2,1,192,48,8,7};
//位图文件的字节掩码序列
unsigned char mask2[13]={252,254,248,252,254,248,252,254,251,252,252,254,248};
//为1时,存放待加密信息的缓存指针加一
int add1[13]={0,0,0,1,0,0,0,0,1,0,0,0,1};
//为1时,存放BMP文件的缓存指针加一
int add2[13]={1,1,1,1,1,1,1,1,0,1,1,1,1};
接下来通过CFile类的Open()、ReadFile()等相关函数将待加密的源文件和用作加密载体的位图文件内容分别读取到缓存buf1和buf2中,在此需要强调的是缓存的类型不能是通常的char型,而必须是unsigned char型,否则会在处理带有汉字的文件时出现错误。如果源文件和载体位图文件的大小不满足1∶4的必要条件会导致空指针的异常错误,所以要在进行加密算法之前先对源文件和载体位图文件的大小进行比较。
下面是部分主要代码:
//将源文件的文件名、文件长度、分割符、内容填入buf3缓存
total=3+FileName1.GetLength()+str.GetLength()+FileLen1;
buf3=new unsigned char[total];
for(int i0=0;i0<FileName1.GetLength()+str.GetLength()+1;i0++)
buf3[i0]=title[i0];
i0++;
//从第39个字节开始存储其他信息
pointer2=38;
buf3[i0]=‘*’;
//把文件内容与加密密钥相异或
for(int i1=0;i1<FileLen1;i1++)
buf3[i0+i1+1]=buf1[i1]^PSW;
while(pointer1<=total)
{
//把将从buf3缓存中提取出来的比特流填入载体位图的每个字节的冗余比特位中
if(move1[pointer3]>0)
buf2[pointer2]=(buf2[pointer2]&mask2[pointer3])|((buf3[pointer1]&mask1[pointer3])>>
move1[pointer3]);
else
buf2[pointer2]=(buf2[pointer2]&mask2[pointer3])|((buf3[pointer1]&mask1[pointer3])
<<(move1[pointer3]*(-1)));
if(add1[pointer3]==1)
pointer1++; //修正指针
if(add2[pointer3]==1)
pointer2++; //修正指针
pointer3++; //修正指针
pointer3%=13;
}
程序跳出While循环时,已经将源文信息和位图信息按协议融合好了,剩下的工作就是调用MFC的CFile类的Write()和Close()成员函数将产生的密文从缓存保存到磁盘上的文件,即含有加密信息的位图文件。
可以通过ACDSee等看图软件比较一下不含任何其他信息的原始位图文件和新产生的融合有密文信息的位图文件,用肉眼是看不出它们的区别的,而且文件长度也没有发生变化。
加密信息的恢复
和大多数加解密程序一样,本方法的解密和加密过程也是很类似的,只要将流程逆转即可。其实现的主要思路是按照协议将作为载体的位图文件中的指定位信息提取出来组成比特流,并将此比特流拆分恢复成原始的源文信息。首先设定好移位值和掩码:
//合并文件信息字节时的移位序列
int move2[13]={6,5,2,0,7,4,2,1,-2,6,4,3,0};
//位图文件字节的掩码序列
unsigned char mask2[13]={3,1,7,3,1,7,3,1,4,3,3,1,7};
//为1时,存放待恢复信息的缓存指针加一
int add1[13]={0,0,0,1,0,0,0,0,1,0,0,0,1};
//为1时,存放含有加密信息的BMP文件内容的缓存指针加一
int add2[13]={1,1,1,1,1,1,1,1,0,1,1,1,1};
然后再通过CFile类的Open()、Read()等文件处理函数将含有加密信息的位图文件内容读取到缓存中,下面进行的就是密文的提取与恢复工作:
//其他文件的信息是从第39个字节开始存放的
pointer2=38;
//buf3缓存清零
memset(buf3,0,FileLen2);
//计算异或运算值
PSW=MakePSW();
while(true)
{
//从位图文件的字节中提取出存储的信息流并重组成待恢复的文件字节序列
if(move2[pointer3]>0)
buf3[pointer1]|=(buf2[pointer2]&mask2[pointer3])<<move2[pointer3];
else
buf3[pointer1]|=(buf2[pointer2]&mask2[pointer3])>>(move2[pointer3]*(-1));
if(add1[pointer3]==1)
{
if(buf3[pointer1]==‘*’)
//分割符记数
times++;
if((pointer1>=0)&&(times==0))
{
CString str;
str.Format(“%c”,buf3[pointer1]);
//提取待恢复的文件名
FileName1+=str;
}
if((times==1)&&(buf3[pointer1]!=‘*’))
{
temp[pointer4]=buf3[pointer1]; pointer4++;
}
if((times==2)&&(buf3[pointer1]==‘*’))
{
temp[pointer4]=0;
//提取待恢复的文件长度
FileLen1=static_cast<DWORD>(atoi
(temp));
pointer4=0;
}
if(times>=2)
{
//通过再次异或运算恢复出源文件内容
buf1[pointer4]=buf3[pointer1]^PSW;
pointer4++;
}
if(pointer4>FileLen1)
//文件恢复完毕则跳出循环
break;
pointer1++; //修正指针
}
if(add2[pointer3]==1)
pointer2++;//修正指针
pointer3++;//修正指针
pointer3%=13;
}
最后,根据已经从位图中提取出来的源文件的文件名、文件扩展名,将解密出来的信息按原来的文件名重新恢复出来。显然如果在解密时输入的密码不正确的话,解出来的信息是无法识别的。
小 结
该方法将重要信息隐藏于位图文件中可以对其起到很好的保护作用,尤其是其载体为普通的24位BMP位图文件,几乎不会有人能意识到里面会含有其他的信息。我们在利用电子 邮件 发送机要文件时可以先将其隐藏于位图之中,然后将此位图以附件形式发送出去,即使邮件被拦截侦听,也不至于造成泄密。
郎锐
01-6-29 上午 09:27:42
--------------------------------------------------------------------------------
24位BMP位图文件
要利用BMP位图进行加密首先需要了解BMP文件的存放格式,24位真彩BMP位图文件包括3部分:
第一部分是BMP文件头:前2个字节是“BM”,是用于识别BMP文件的标志;第3、4、5、6字节存放的是位图文件的大小,以字节为单位;第7、8、9、10字节是保留的,必须为0;第11、12、13、14字节给出位图阵列相对于文件头的偏移。
第二部分是点位图 信息 :从第29个字节开始,第29、30字节描述的是像素的位数;第35、36、37、38字节确定图像字节数的多少,但通常此项为空。
第三部分是位图阵列:从第39个字节开始,每3个字节表示一个像素,这3个字节依次表示该像素的红、绿、蓝亮度分量值。要从位图文件中“挤"出用来隐藏其他重要信息的存储空间,就需要从这里入手。
实现原理
根据亮度公式:I=0.3R+0.59G+0.11B可以知道人眼对于绿色分量最为敏感,对蓝色分量最不敏感,绿色分量值改变1个单位就相当于蓝色分量改变5个、红色分量改变2个单位。折算为二进制,红色分量可以改变其低两位、绿色分量可以改变其最低位、蓝色分量可以改变其低三位。这样就可以从描述每个像素的3个字节中得到2+1+3=6个比特的冗余信息位用于存储其他文件的比特流。按此比率,从一幅24位BMP位图中可以得到的最大存储空间为:((位图文件大小-40)×6)/(3×8),大约是其位图文件长度的1/4。
由于位图文件中每3个字节只能提供6个比特的存储空间,所以需要12个字节才能完整保存源文件的3个字节。
本文制定的加密协议是先通过程序窗口输入密码,从位图文件的第39个字节即位图阵列部分开始存储,依次为:源文件名、分割符“*”、源文件的长度、分割符“*”、源文件的内容。其中源文件内容的每个字节需与密码相异或,以起到对文件进一步加密的效果。
这里需要特别指出的是:如果在 网络 传输时该文件被截获,从中将加密文件恢复出来的可能性是非常小的。首先,从外观上加密后的文件和一般的BMP文件没有任何区别,而且加密前后位图文件大小也不会发生变化,不会引起截获者的注意。其次,由于只有大约1/4的空间存有隐含信息,对于被加密信息而言,其他3/4的内容都是冗余位。在密码学上,往往采取加大冗余位的方式来 提高 加密的可靠性,显然这种拥有75%的冗余码的加密方法还是颇为可靠的。最后,破译者并不知晓加密所采取的协议,而且待加密的内容都同加密密钥经过了异或运算,在没有正确解密密码的情况下也是很难破译的。
源文件的加密
由于进行加密处理的过程中要频繁进行位运算,所以选用在这方面较有优势的Micorsoft Visual C++ 6.0作为开发工具。
加密的关键是将待加密信息组成比特流,并将其按规定的格式依次填入载体位图文件的指定位中。首先要做好准备工作,由于每12个位图文件的字节恰好可以用于保存3个源文件的字节,而12又是3的整数倍,所以可以设定每12个字节为一个循环。下面5个数组中存放的数据用于处理每个循环中的各个字节所要进行的移位量和掩码值等:
//填冗余位时的移位序列
int move1[13]={6,5,2,0,7,4,2,1,-2,6,4,3,0};
//源文件的字节掩码序列
unsigned char mask1[13]={192,32,28,3,128,112,12,2,1,192,48,8,7};
//位图文件的字节掩码序列
unsigned char mask2[13]={252,254,248,252,254,248,252,254,251,252,252,254,248};
//为1时,存放待加密信息的缓存指针加一
int add1[13]={0,0,0,1,0,0,0,0,1,0,0,0,1};
//为1时,存放BMP文件的缓存指针加一
int add2[13]={1,1,1,1,1,1,1,1,0,1,1,1,1};
接下来通过CFile类的Open()、ReadFile()等相关函数将待加密的源文件和用作加密载体的位图文件内容分别读取到缓存buf1和buf2中,在此需要强调的是缓存的类型不能是通常的char型,而必须是unsigned char型,否则会在处理带有汉字的文件时出现错误。如果源文件和载体位图文件的大小不满足1∶4的必要条件会导致空指针的异常错误,所以要在进行加密算法之前先对源文件和载体位图文件的大小进行比较。
下面是部分主要代码:
//将源文件的文件名、文件长度、分割符、内容填入buf3缓存
total=3+FileName1.GetLength()+str.GetLength()+FileLen1;
buf3=new unsigned char[total];
for(int i0=0;i0<FileName1.GetLength()+str.GetLength()+1;i0++)
buf3[i0]=title[i0];
i0++;
//从第39个字节开始存储其他信息
pointer2=38;
buf3[i0]=‘*’;
//把文件内容与加密密钥相异或
for(int i1=0;i1<FileLen1;i1++)
buf3[i0+i1+1]=buf1[i1]^PSW;
while(pointer1<=total)
{
//把将从buf3缓存中提取出来的比特流填入载体位图的每个字节的冗余比特位中
if(move1[pointer3]>0)
buf2[pointer2]=(buf2[pointer2]&mask2[pointer3])|((buf3[pointer1]&mask1[pointer3])>>
move1[pointer3]);
else
buf2[pointer2]=(buf2[pointer2]&mask2[pointer3])|((buf3[pointer1]&mask1[pointer3])
<<(move1[pointer3]*(-1)));
if(add1[pointer3]==1)
pointer1++; //修正指针
if(add2[pointer3]==1)
pointer2++; //修正指针
pointer3++; //修正指针
pointer3%=13;
}
程序跳出While循环时,已经将源文信息和位图信息按协议融合好了,剩下的工作就是调用MFC的CFile类的Write()和Close()成员函数将产生的密文从缓存保存到磁盘上的文件,即含有加密信息的位图文件。
可以通过ACDSee等看图软件比较一下不含任何其他信息的原始位图文件和新产生的融合有密文信息的位图文件,用肉眼是看不出它们的区别的,而且文件长度也没有发生变化。
加密信息的恢复
和大多数加解密程序一样,本方法的解密和加密过程也是很类似的,只要将流程逆转即可。其实现的主要思路是按照协议将作为载体的位图文件中的指定位信息提取出来组成比特流,并将此比特流拆分恢复成原始的源文信息。首先设定好移位值和掩码:
//合并文件信息字节时的移位序列
int move2[13]={6,5,2,0,7,4,2,1,-2,6,4,3,0};
//位图文件字节的掩码序列
unsigned char mask2[13]={3,1,7,3,1,7,3,1,4,3,3,1,7};
//为1时,存放待恢复信息的缓存指针加一
int add1[13]={0,0,0,1,0,0,0,0,1,0,0,0,1};
//为1时,存放含有加密信息的BMP文件内容的缓存指针加一
int add2[13]={1,1,1,1,1,1,1,1,0,1,1,1,1};
然后再通过CFile类的Open()、Read()等文件处理函数将含有加密信息的位图文件内容读取到缓存中,下面进行的就是密文的提取与恢复工作:
//其他文件的信息是从第39个字节开始存放的
pointer2=38;
//buf3缓存清零
memset(buf3,0,FileLen2);
//计算异或运算值
PSW=MakePSW();
while(true)
{
//从位图文件的字节中提取出存储的信息流并重组成待恢复的文件字节序列
if(move2[pointer3]>0)
buf3[pointer1]|=(buf2[pointer2]&mask2[pointer3])<<move2[pointer3];
else
buf3[pointer1]|=(buf2[pointer2]&mask2[pointer3])>>(move2[pointer3]*(-1));
if(add1[pointer3]==1)
{
if(buf3[pointer1]==‘*’)
//分割符记数
times++;
if((pointer1>=0)&&(times==0))
{
CString str;
str.Format(“%c”,buf3[pointer1]);
//提取待恢复的文件名
FileName1+=str;
}
if((times==1)&&(buf3[pointer1]!=‘*’))
{
temp[pointer4]=buf3[pointer1]; pointer4++;
}
if((times==2)&&(buf3[pointer1]==‘*’))
{
temp[pointer4]=0;
//提取待恢复的文件长度
FileLen1=static_cast<DWORD>(atoi
(temp));
pointer4=0;
}
if(times>=2)
{
//通过再次异或运算恢复出源文件内容
buf1[pointer4]=buf3[pointer1]^PSW;
pointer4++;
}
if(pointer4>FileLen1)
//文件恢复完毕则跳出循环
break;
pointer1++; //修正指针
}
if(add2[pointer3]==1)
pointer2++;//修正指针
pointer3++;//修正指针
pointer3%=13;
}
最后,根据已经从位图中提取出来的源文件的文件名、文件扩展名,将解密出来的信息按原来的文件名重新恢复出来。显然如果在解密时输入的密码不正确的话,解出来的信息是无法识别的。
小 结
该方法将重要信息隐藏于位图文件中可以对其起到很好的保护作用,尤其是其载体为普通的24位BMP位图文件,几乎不会有人能意识到里面会含有其他的信息。我们在利用电子 邮件 发送机要文件时可以先将其隐藏于位图之中,然后将此位图以附件形式发送出去,即使邮件被拦截侦听,也不至于造成泄密。