如果cocos项目没有对资源进行加密处理,发布出来的APK一旦被人解包,则所有图片资源都会暴露出来,为了避免图片资源被人恶意使用,所以我准备给自己项目中使用到的图片进行简单加密,这样可以防住一部分解包伸手党。
我们这里采用最常见的异或加密,异或加密性质:一个数异或同一个数两次,得到的是本身。根据这个性质,我们可以采用把图片的字节流进行异或加密,只需要设置一个Key,在本地客户端使用Key进行一次异或,然后在cocos导入文件的函数中再使用Key进行一次异或,即可还原。
同时为了兼容没有加密的图片,我们需要再加密过后的文件头上添加一下标识,比如文件头部添加N个自定字节流来表示该图片别加密过,同时除了添加的头标识外的其他字节流全部或者部分进行异或加密。
Python进行图片文件加密
根据上面的思路,首先我们需要把项目中使用到的图片进行字节流加密,这里我为了方便就采用python来做,比较简单快捷。你也用C++,java等等你擅长的语言进行文件操作。
#!/usr/bin/env python
#coding=utf-8
# 2019-12-28 by LeiTao
#
import sys
import osFileEncrpyEnum = {".png"} #这里填入你需要加密图片的后缀名,这里我只加密png
EncrpyKey = 58 #异或加密秘钥
HeadSize = 8 #添加8个byte在文件头
EncrySize = 100 #为了更快的解密,只加密文件的100个字节流
def MakeEncrpy(pahtName,FileName):oldfile = open(os.path.join(pahtName, FileName),'rb')oldConten = oldfile.read()oldfile.close()li_out=[]#添加加密标识for index in range(HeadSize):li_out.append(chr(0x01))for i in range(len(oldConten)):if i < EncrySize:li_out.append(chr(ord(oldConten[i])^EncrpyKey)) #异或Key进行加密else:li_out.append(chr(ord(oldConten[i])))newfile = open(os.path.join(pahtName, FileName), 'wb')newfile.truncate()newfile.write(''.join(li_out))newfile.close()returndef main():dirPath = sys.argv[1]print dirPathprint "==============GameEncrpy Begin"for root, dirs, files in os.walk(dirPath):# root 表示当前正在访问的文件夹路径# dirs 表示该文件夹下的子目录名list# files 表示该文件夹下的文件list# 遍历文件for f in files:ExtensionName = os.path.splitext(f)[1]if ExtensionName in FileEncrpyEnum :MakeEncrpy(root,f)print "==============GameEncrpy End"# -------------- main --------------
if __name__ == '__main__':try:main()except Exception as e:sys.exit(1)
上面的代码就能够批量对文件夹中后缀为png的图片进行处理,我这里边选择在文件头上插入8个0x01,然后对文件前100个字节进行加密。
这是加密前后的对比,加密后的图片因为里面的内容已经被改变,所以正常已经打不开了,这也就达到我们加密的目的。
COCOS CCFileUtils解密
图片加密过后,COCOS也是无法识别的如果直接引用则会出现错误。所以我们现在开始做COCOS部分的解密。
阅读过源代码的同学应该都知道,COCOS内最终获取图片字节流的函数是写在CCFileUtils的getDataFromFile中,不熟悉源代码的同学也可以自己断点进去调试跟踪,这里我们直接开始修改加载的代码。
static Data getData(const std::string& filename, bool forString)
{if (filename.empty()){return Data::Null;}CCLOG("FileUtils getData Now"); Data ret;unsigned char* buffer = nullptr;size_t size = 0;size_t readsize;const char* mode = nullptr;if (forString)mode = "rt";elsemode = "rb";auto fileutils = FileUtils::getInstance();do{// Read the file from hardwarestd::string fullPath = fileutils->fullPathForFilename(filename);FILE *fp = fopen(fileutils->getSuitableFOpen(fullPath).c_str(), mode);CC_BREAK_IF(!fp);fseek(fp,0,SEEK_END);size = ftell(fp);fseek(fp,0,SEEK_SET);if (forString){buffer = (unsigned char*)malloc(sizeof(unsigned char) * (size + 1));buffer[size] = '\0';}else{buffer = (unsigned char*)malloc(sizeof(unsigned char) * size);}readsize = fread(buffer, sizeof(unsigned char), size, fp);fclose(fp);if (forString && readsize < size){buffer[readsize] = '\0';}} while (0);if (nullptr == buffer || 0 == readsize){CCLOG("Get data from file %s failed", filename.c_str());}else{//这里实际上就是最终文件读取后的buffer//所以我们需要再这里进行校验文件是否需要解密/////检查是否需要解密std::string::size_type index = fullPath.find_last_of(".") + 1;std::string fileType = fullPath.substr(index, fullPath.length() - index);if(fileType.compare("png")==0) //png类型进行解密{CCLOG("Decode Now");unsigned char encry_head[8] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};//添加的头size_t HeadSize = 8;if (memcmp(buffer, encry_head, HeadSize) == 0) //比较是否我们添加的头,来判断是否需要解密{int Key = 58;int EncrySize = 100;unsigned char *new_buffer = nullptr;if (forString){new_buffer = (unsigned char*)malloc(size - HeadSize + 1);new_buffer[size - HeadSize] = '\0';}else{new_buffer = (unsigned char*)malloc(size - HeadSize);}for (int i = HeadSize; i < size; i++) {if (i < EncrySize + HeadSize) { //加密部分进行解密new_buffer[i - HeadSize] = buffer[i] ^ Key; //异或Key进行解密}else {new_buffer[i - HeadSize] = buffer[i];}}free(buffer);buffer = new_buffer;size = size - HeadSize; //去掉加密头长度}}ret.fastSet(buffer, readsize);}return ret;
}
修改完getData函数之后,我们就可以愉快的使用我们加密后的图片啦。最后要注意的点是cocos 的跨平台性质,所以你如果你要在win32调试的话记得把CCFileUtils-win32.cpp中的也一并修改了。