本文乃Siliphen原创,转载请注明出处:http://blog.csdn.net/stevenkylelee
本文针对的是cocos2d-x 3.4 版本进行研究。
首先,加密解密应当是1个单独的话题,1般不会触及具体使用的引擎、框架和技术。
加密算法有Base64,DES等。
Base64的原理类似于凯撒密码,啥是凯撒密码呢,就是1个字符用另外一个字符来代替。
比如:a用i代替,b用k代替,以此类推。加密和解密的进程就是1个相互映照的进程。
DES是1种使用密钥的加密算法。
DES和Base64这类无密钥的算法在使用上的区分是:
DES算法本身是可以不保密的,只要保密密钥便可,密钥才是解密的关键。
Base64如果用于加密的话,算法本身就是密钥。要保密算法。
MD5,SHA等是摘要算法。
Base64,DES都可以把密文还原出明文。
而MD5,SHA则不能对算法作用后的输出还原出原始数据。
摘要算法1般做身份验证。
我们做资源加密是要选择能把密文还原出明文的算法。
这属于密码学的内容了,可深可浅。
可以鉴戒凯撒密码的原理自己对每一个字节的内容进行映照来加密。
也能够对1字节,2字节,4字节的数据,用某个数进行异或运算来加密。
异或的特性是,用数A对内容B进行异或得到内容C,再用数A对内容C异或可以得到内容B。
这个特性可以被用来加密。同时,数A的作用就是密钥了。
设计的加密算法不同,加密算法的使用的接口也不同。
如果是用上面的2种思路做加密算法的话,加密算法的实现可以“in place”操作,
就是可以直接在密文所在的内存进行解密,不单独分配内存来保存解密后的数据。
如果加密算法很复杂,没法“就地还原”,就需要新申请内存来保存解密数据。
固然,履行速度1般是”就地还原“快。
我觉得,如果可以的话,最好是自己设计加密算法。
使用现成的DES之类的算法,密钥是关键。
如果是自己设计1个用密钥加密的算法,密钥和算法本身都是不公然的。
破解者要获得资源明文要经过以下步骤:
1.在程序中找到密钥的常量。
2.看懂反出来的汇编还原出算法,或想办法利用加密算法的汇编代码。
其中步骤2会加大解密者解密的难度,这是自己设计加密算法比用现成的DES之类著名算法的好处。
我见过有1些人加密是对字段内容进行加密。
比如,他用xml保存数据,他只对保存的值进行加密。
内容类似这样 <Entity Hp = "密文" AttackValue = "密文" >
这其实不是1种好的应用加密的方式,由于这类方式会:
1.流露程序的配置文件结构。XML、json等。
2.流露了程序可能使用的数据结构。看以上内容可料想Entity实体类有2个字段,Hp(血量),AttackValue(攻击力)
更完全的加密方式是,对全部xml、json等文件进行加密。
这样他人就不会知道关于你的数据的1丁点信息。
这类做法不是直接用数据解析库封装的类似LoadFile的函数直接读取文件。
而是,先把加密文件用文件读取方法读入内存,在内存解密后,再把解密数据传给解析库的解析函数。
这类做法要求解析库有对内存进行解析的接口。
比如,若用XML解析库,就会弃用LoadFile,要求有Parse方法。
有1种情况可能没法对全部数据文件进行加密。
就是使用sqlite等数据库的时候,这时候候只能对字段进行加密了。
有些人设计加密机制会要求配置程序本身。
比如:程序需要1个配置(1个开关变量或条件编译)来决定是不是对资源进行解密。
发布时,把资源都加密好,开出发序对资源的解密。
开发时,资源没必要加密,屏蔽程序对资源的解密。
这类做法需要来回设置解密开关,比较繁琐。
有1种实现思路更有扩大性、更灵活、使用更方便。
那就是自己设计文件头。
熟习Win32编程的人可能会知道PE文件格式。
Windows运行1个exe之前,会检查PE文件头,
获得EXE文件的数据常量、代码区块等信息后,系统才能正确履行1个EXE。
我们可以设计1个加密文件头,加密文件头有1个字段标识该文件是不是是被加密过的。
解密算法可以通过判断加密头的标识符来决定是不是进行解密、采取哪一种解密算法等。
这是1种数据驱动式的解密机制,解密信息都保存在加密数据本身中。
对没有被加密过的数据,程序判断没有文件加密头标识,就不履行解密。
这样,程序可以同时读取未加密的数据和已加密的数据,而不需要对程序进行任何修改。
注意:加密头的标识符应当足够特殊,保证和未加密的数据不会产生冲突。
若未加密的数据恰好出现了加密头的内容,那末程序就会对未加密的数据进行解密。
我的项目配置是用CSV文件。解析器,用的是我自己写的CSV解析器:
《CSV文件格式解析器的实现:从字符串Split到FSM》
在游戏中这部份的解密,比较容易实现,就是对全部文件加密,
游戏启动时把加密文件读入内存进行解密,然后把解密数据传到解析器进行解析。
1些由游戏产生的需要保存的数据,我用XML进行保存。加解密思路也如上。
但是,对图片、动画,粒子、cocostudio1.6导出的UI 等数据,加密就不那末简单了。
由于,像 Sprite::create 这样的接口,并没有提供1个从内存获得纹理数据来初始化精灵的方式,
而是直接从磁盘文件中读。用户在外部是没法干预文件数据的读取载入的。
游戏资源的加密要怎样做?――只有修改引擎了。- -!
我觉得,修改引擎其实不是1个好的做法。
引擎应当由引擎提供商来保护,而不是靠用户自己来动手。
用户自己动手会造成项目保护升级困难。
自己把引擎改得乱78糟,想升级引擎到新的版本会变成很麻烦。
就算是修改了1点,升级引擎版本1次,就要做1次修改,这样也很麻烦。
要修改引擎,首先要跟踪调试,
以cocos2d-x 3.4 作为实验版本,从Sprite::create入口,1步步跟下去,
看看引擎是在哪里载入文件数据的。
经过调试跟踪,Sprite::create的调用堆栈以下:
getData
FileUtilsWin32::getDataFromFile
Image::initWithImageFile
TextureCache::addImage
Sprite::initWithFile
Sprite::create
在windows上,精灵载入图片,是调用到了
CCFileUtils-win32.cpp 这个文件中的
static Data getData(const std::string& filename, bool forString) 函数。
这个函数是1个非类成员静态函数。
再调试下其他的数据载入,cocostudio1.6做的动画的调用堆栈是:
cocos2d::FileUtilsWin32::getFileData
cocostudio::DataReaderHelper::addDataFromFile
cocostudio::ArmatureDataManager::addArmatureFileInfo
经过了多种文件类型载入的跟踪调试后,总结,
基本上文件的载入函数有2个,都在CCFileUtils-win32.cpp文件中,
1.FileUtilsWin32::getFileData
2.getData
要做解密的话,就是修改这2个函数了。
值得注意的1点是:getData函数的签名是:
static Data getData(const std::string& filename, bool forString)
forString这个参数,用于标识读入的文件是否是1个文本文件。
如果forString为真的话,buffer就要多分配1个字节,用来放置C语言的字符串结束符
截图1下引擎的代码:
要做解密,我们的解密数据内存也要用forString来判断是不是多分配1个字节的内存。
FileUtils单件类,很多函数是虚函数。FileUtils::getInstance,
在Windows平台上,返回的是 FileUtilsWin32子类对象。
在Android平台上,返回FileUtilsAndroid子类对象。
Cocos2d-x 3.4 实现在Windows和Android平台上的解密需要修改2个文件:
Windows 平台的:cocosplatformwin32CCFileUtils-win32.cpp
Android 平台的:cocosplatformandroidCCFileUtils-android.cpp
这2个文件里面都有 getData 和 getFileData 函数,都需要修改之。
文件数据的读取在不同平台上有不同的实现,
如果要在目标平台上做资源加密,就需要修改目标平台上的读取实现。
FileUtils是1个文件读取基类,提供有默许的实现,1些平台会提供特定的实现。
通过量态,FileUtils让我们的游戏客户端代码不需要对特定平台做特定处理,
也不需要关心FileUtils子类的类型。
比较遗憾的是,由于时间关系,目前我没有找到对音频文件解密的方法。
调试跟踪发现,Cocos2d-x 在Windows上播放音频是直接调用了Win32 API,这些接口API是输入1个文件路径来播放音频的。
之前有提到,做加密解密需要利用数据的接口从内存中获得数据。
接口直接读磁盘文件,就没有办法干预文件的载入进行数据解密了。
我觉得引擎的设计者,可以提供1个接口,利用视察者模式,
让引擎用户可以在不修改引擎本身的情况下,干预数据的读取进行数据变换来实现解密。
毕竟自己改引擎不是1个好的做法。
1般要做资源数据加密,大体会做2件事:
1.写1个工具,能对磁盘文件进行加密。这个工具,可能需要遍历文件夹、可批量选择文件等功能。
2.在软件程序中加入解密功能,对工具加密后的数据进行解密。
工具能方便操作、提高生产力,很重要。
这里,我自己做了2个工具。
1.对cocos2d-x引擎进行修改和恢复的工具,使其具有解密功能。以下:
2.对磁盘文件进行加密解密的工具,方便打包时的批量挑选操作。以下:
这个工具能辨认出文件是不是已被加密,原理是用了之前说的”加密文件头“设计。
这样可以避免对已被加密后的文件再次履行加密,避免对未被加密的文件履行解密。
下降误操作的可能性同时,也直观地显示文件的加密状态。
如果没有”加密文件头“,工具程序就没法辨认1个文件究竟是否被加密过。
做了资源加密会下降程序的性能,缘由很简单,需要对文件数据进行额外的处理。
略微复杂1点的加密算法,可能要申请内存空间来保存解密数据。delete , new 都是耗时操作。
资源的加密也可能会使得apk包体体积增大。这是为何呢?
缘由是,用Cocos2d-x 开发,资源会有很多*.plist *.ExportJson *.tmx 等文件。
plist 多是配置,也多是粒子。
*.ExportJson 等文件是 Cocostudio1.6 做出的动画、UI 导出文件。
*.tmx 是Tilemap输出的地图格式。
以上说的这些文件,都是用文本存储数据的,可以用Windows记事本打开查看内容。
apk包实际上是1个紧缩包,紧缩算法对有大量冗余数据的文件紧缩率是很高的。
我之前写过1篇关于紧缩算法的文章《文件紧缩与解压:哈夫曼编码》有兴趣的同学可以看看。
我做过1个实验,600多KB的 UI *.plist *.ExportJson 文件,打成apk包后,实际上只占60多KB。
这是由于紧缩算法能对这些配置的文本文件有很高的紧缩率。
如果我们的加密算法会下降冗余数据。
比如:1个文件的内容是“111111....",10万个1。
我们的加密算法为了增加安全性,会把这些内容加密成为:"sfsadfsa...."。
解密者很难从无规律的不重复的数据推出本来的”111111....“的内容,
但同时对这样的数据,紧缩率也会很低。
我之前设计了1个使用密钥加密的算法,会消除冗余数据,
对都是一样的1个字符的内容,可以加密成1个看似无规律的乱78糟的内容,
其实还是有规律可言,只是无规律循环节很大而已。
然后,这些加密后的数据,几近就不能紧缩了。
加密资源以后输出的apk包相比没加密资源的包,体积增加很多。
对这类情况,可以仅对图片等关键资源加密,而不对非关键性的文本文件加密。
或下降算法的加密强度,保存数据冗余性。