原文链接 https://yq.aliyun.com/articles/72267
背景
随着手机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文件头组成:
变量名 大小 作用 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); }4350338-33116ce33bc6f997.gif
后续:
这只是相对最简单的图像加密,图像并压缩后容易出现损失,因此,后面要加上特征值作为验证,还有应该有一定冗余,还需要部分对其做成类似噪点的研究。