背景
随着手机APP越来越多,对于APP信息安全面临的挑战越来越大,像接口传递的验证信息这些相对保密的信息如果直接放在app中明文,那么毫无疑问,很容易就被破解出来,想干嘛就干嘛。因此为了对部分本地信息加密,想出过无数的办法,本次讨论的重点,无水印信息图片加密。**
原理
无水印信息图片加密,基本原理,就是将信息负载再图片上,然后程序通过特定的算法将信息再度拿出来,而图片一看下去也是正常显示的,肉眼看不出任何的猫腻。那么到底怎么附加法?
1. 追加信息法:
利用不同格式图片的特性,例如 BMP文件头标记了图片文件大小,后面信息不读取,或JPG文件拥有FFD9 标志结束符,因此就算将再多信息附加上去,也不会影响原来图片查看。
2.颜色特征法:
根据 颜色的特点,因为颜色的最后一个位含有的信息量就算改变也不会改变大局,所以颜色的最后一个bit作为信息记录点。
3.颜色特征法Ex:
颜色特征,按一定的算法,获取约定的图片特征,例如,都获取RGB中G的整张图的波形,通过某种滤波器,分析出来某段,然后加上校验码进行校验信息是否有效,而且多段,含冗余,分布图片各个地方,几时压缩,或者截图后,信息也有可能被获取到。
各自优缺点
追加信息法:
优点:
加密后图片正常显示,无信息长度限制,可以无限追加信息。我们都不明白为啥某个 “正常” 图片竟然有1,2G那么大,到底后面附加了什么???嘿嘿
缺点:无限追加,也是致命缺点,你不会傻到真的认为 阿强那张1,2G的图片真的只是单纯的图片那么简单吧?
颜色特征法:
优点:
加密后图片正常显示,信息保密度更强,不会增加图片本身的大小,当然转格式例外,而且根据算法,整体的保密性更强。
缺点:
能加密得信息的长度受图片size限制,如果对图片进行过压缩,信息将会损失得一塌糊涂。**
颜色特征法EX:
优点:
经过压缩后,信息仍有机会提取出来,耐操,加密后不会改变图片大小,有冗余信息,破解难度大。
缺点:
图片容易显示不正常,当然搞成类似白噪点也是个技术活,能加密得信息的长度受图片size限制。
颜色特征法原理剖析
这里重点解释下颜色特征法是怎么实现得。
颜色原理
说之前,必须要说下颜色的组成。大家都知道平时开发中我们使用的颜色值例如白色 #FFFFFFFF 黑色 #00000000 这些数字代表什么呢?
**
他们以2位16进制数字位一个单位分别代表 A,R,G,B。记得在保存的时候别忘记了A,透明度,否则出来的都是黑色一片哦。**
这里讨论 R,G,B,他们代表红,绿,蓝,三原色,**
而2位16进制的数字联合代表256个色值,换算2进制就是 8位。因为主要决定颜色的信息其实都储藏在这里,而前面的值表示颜色的变化越大,而最后以为相对改变的话,对颜色本身的影响是非常小的,255和254是相差很小的颜色变化。**因此只要我们改变三原色随便一个或者几个的最后一位,其实对颜色变化影响微乎其微。肉眼压根不能看出变化。
int rgb = image.getPixel(curX, curY);
r = (rgb & 0x00ff0000) >> 16;
g = (rgb & 0x0000ff00) >> 8;
b = (rgb & 0x000000ff);
al = (rgb & 0xff000000) >> 24;
if (bitLength >= 0) {
switch (iRGB) {
case 0:
r = (r & 0x000000FE);
r |= value;
break;
case 1:
g = (g & 0x000000FE);
g |= value;
break;
case 2:
b = (b & 0x000000FE);
b |= value;
break;
}
}
rgb = al << 24 | (r << 16) | (g << 8) | b;
图片格式原理
如果你以为只是改个颜色值,就大功告成,呵呵,那你马上哭着发现,压根你加密的信息从来就没正确拿出来过。因为图片是含有头部信息的,而且不同格式的图片头信息肯定也不一致的,相对固定的头部是BMP图片的,因此俺们这次也是采用输出BMP图片作为加密后的结果图片。首先我们看看BMP文件头组成:
bmp文件头
变量名 | 大小 | 作用 |
---|---|---|
bfType | 2bytes | 默认直接写死 424d 说明文件类型的 |
bfSize | 4bytes | 图片总大小,包括头信息 |
bfReserved1 | 2bytes | 保留,必须设置为0 |
bfReserved2 | 2bytes | 保留,必须设置为0 |
bfOffBits | 4bytes | 说明文件头开始到实际图片数据之间的偏移量,其实也是相对恒定的 |
位图信息头
变量名 | 大小 | 作用 |
---|---|---|
biSize | 4bytes | BitmapInfoHeader结构需要的字数,固定的40 |
biWidth | 4bytes | 图像的宽度,用像素为单位 |
biHeight | 4bytes | 图像的高度,用像素为单位。还有个作用,标志图片是正向还是倒向的。如果该值是正数,说明图像是倒向的,如果该数是负数,那么图像是正向的 |
biPlanes | 2bytes | 为目标设备说明颜色的平面数,他的值总是设为1 |
biBitCount | 2bytes | 说明比特数/像数,其值为1、4、8、16、24、32,现在通常用24位 |
biCompression | 4bytes | 说明图像数据压缩的类型。 0 表示不压缩 1 表示8比特编码,只用于8位图 |
biSizeImage | 4bytes | 图像大小,单位为字节 |
biXpelsPerMeter | 4bytes | 说明水平分辨率,像素/米 表示 |
biYPelsPerMeter | 4bytes | 说明垂直分辨率,像素/米 表示 |
biClrUsed | 4bytes | 说明位图实际使用的彩色表中的颜色索引数 |
biClrImportant | 4bytes | 说明对图像显示有重要影响的颜色索引的数目如果是0,表示都很重要 |
so,在修改完图像信息后,需要将这些信息补上头信息,再将颜色信息附上,关键代码如下
补充头信息:
FileOutputStream fileos = new FileOutputStream(filename);
// bmp文件头
int bfType = 0x4d42;
long bfSize = 14 + 40 + bufferSize;
int bfReserved1 = 0;
int bfReserved2 = 0;
long bfOffBits = 14 + 40;
// 保存bmp文件头
writeWord(fileos, bfType);
writeDword(fileos, bfSize);
writeWord(fileos, bfReserved1);
writeWord(fileos, bfReserved2);
writeDword(fileos, bfOffBits);
// bmp信息头
long biSize = 40L;
long biWidth = nBmpWidth;
long biHeight = nBmpHeight;
int biPlanes = 1;
int biBitCount = 24;
long biCompression = 0L;
long biSizeImage = 0L;
long biXpelsPerMeter = 0L;
long biYPelsPerMeter = 0L;
long biClrUsed = 0L;
long biClrImportant = 0L;
// 保存bmp信息头
writeDword(fileos, biSize);
writeLong(fileos, biWidth);
writeLong(fileos, biHeight);
writeWord(fileos, biPlanes);
writeWord(fileos, biBitCount);
writeDword(fileos, biCompression);
writeDword(fileos, biSizeImage);
writeLong(fileos, biXpelsPerMeter);
writeLong(fileos, biYPelsPerMeter);
writeDword(fileos, biClrUsed);
writeDword(fileos, biClrImportant);
最后把图像信息也附上去。
for (int nCol = 0, nRealCol = nBmpHeight - 1; nCol < nBmpHeight; ++nCol, --nRealCol)
for (int wRow = 0, wByteIdex = 0; wRow < nBmpWidth; wRow++, wByteIdex += 3) {
int clr = bitmap.getPixel(wRow, nCol);
bmpData[nRealCol * wWidth + wByteIdex] = (byte) Color.blue(clr);
bmpData[nRealCol * wWidth + wByteIdex + 1] = (byte) Color.green(clr);
bmpData[nRealCol * wWidth + wByteIdex + 2] = (byte) Color.red(clr);
}