前言
本方案基于Cocos Creator 3.x,支持图片、文本等多种资源类型的加解密,由于时间有限,目前仅实现了png资源的加密,其他资源类型,将陆续补充与完善。
需求
Cocos Creator 打包产生的包体默认对图片、文本等资源是不加密的。因此如果想要获取游戏的包体的资源,是轻而易举的。
例如对于apk包体,只需要对包体解压,即可非常轻松地获得资源。
对资源进行加密,能够提高破解的门槛,使得他人获取资源“不那么容易”。但因运行环境在本地,无法完全防止破解,加密本质上是与破解者之间的博弈,尽可能提高破解者的成本。
破解基本原理
初级破解玩家:
- 1.解压包体,提取游戏asset,获取所有资源
- 2.如果资源有加密,则尝试寻找市面上已有的解密工具进行尝试,如果失败遂放弃。
中级破解玩家:
- 1.解压包体,提取游戏asset,获取所有资源
- 2.如果资源有加密,则寻找市面上已有的解密工具进行尝试
- 3.现成工具无法完成解密,通过内存地址跟踪获取密钥,通过反汇编等手段找到解密方法,手动编写解密工具或者脚本进行批量解密,或者想办法利用现有代码。
高级破解玩家:
- 1.解压包体,提取游戏asset,获取所有资源
- 2.如果资源有加密,则寻找市面上已有的解密工具进行尝试
- 3.现成工具无法完成解密,通过内存地址跟踪获取密钥,通过反汇编等手段找到解密方法,手动编写解密工具或者脚本进行批量解密,或者想办法利用汇编代码。
- 4.解密还原js代码,改写代码,换皮,上线流通。
对策
针对初级、中级、高级破解玩家,简单的加解密方法:
-
1、远程加载资源并设置token验证,该方法不算为真正的加密方法,但也可以阻挡只会解包体的破解玩家。某些资源可以等到需要使用的时候再加载,迫使破解者通关才可获取资源,提高破解者获取资源的时间成本。
-
2、对资源进行去除文件头、文件尾后,进行加密。游戏运行读取资源时,再进行解密,因该过程需要进行运算,会使得资源加载时间有所延长。
可以采取偏移运算、异或运算或多种运算组合。也可以采取AES加密运算等其他的凯撒密码加密方法(摘要算法,不可还原,主要用于身份验证),但会导致更长的加载时间。
- Q:“为什么要去除文件头、文件尾后?”
- A:压缩体积(实际上深入研究png,还有一些冗余数据块可以去掉),与改变后缀名配合使用,混乱资源,破解者需要反编译代码找回后缀对应关系,编写代码还原文件头,增加破解成本,同时开发者也增加了维护成本,但其实作为通用库维护,成本也算还好。
-
3、另外可以采取网络获取密钥、多变量拼接密钥等方式增加破解者获取密钥的难度,通过代码混淆的方式增加破解者理解解密算法的成本。
实现
加密流程概览
其中图中的自定义加密方法:
自定义加密方法:
- 1.偏移
- 2.异或
- 3.第一点与第二点组合
- 4、AES、DES等凯撒加密方法(运算量相对前击中更大一些,解密CPU消耗高,对加载时长的影响更明显)
遍历资源文件
目标文件类型判断、判断是否已加密,防止重复加密
类型文件特殊处理,可选择进行冗余数据块去除
进行资源加密并重新写入数据
加密结果
进行加密后,即使进行解包,获取的资源文件也无法直接使用,必须获得密钥及解密方法后,编写解密工具进行批量解密后才可完成破解。
解密
对资源进行加密后,在游戏内直接读取资源,则无法解析。需要根据对应的加密密钥与加密方法,编写对应的解密流程进行解析后方可使用。
解密过程基本与加密过程对称,关键点在于定位程序读取文件数据的代码位置,改造代码,将数据还原为加密前的状态。
同时支持保留未加密资源的加载,不影响未加密资源的加载。
解密流程概览
加载流程解析
对解密流程有了基本的认识后,下面就来研究如何在引擎的资源加载流程中增加解密流程,对加密后的资源进行解析。
通过对引擎代码的跟踪,我们发现所有的资源加载操作,都将进入管线,由管线完成加载的流程。
翻看官方文档关于管线的内容:
根据文档进一步跟踪到下载与解析的文档:
发现可以文件类型自定义处理方法,我们可以利用这点功能实现解密流程的接入,这里只演示png资源的流程,更多资源后续加入。
web端实现
因为启动流程的脚本加载顺序如下:
-
- cocos引擎
-
- 插件脚本
-
- 普通脚本
因为Cocos编译后的工程或者包体,资源文件名无规则,所以场景资源等也会被加密脚本统一加密。而场景资源等又不便于控制加载流程。
因此将注册代码作为插件代码加入到引擎工程中,可以实现早于资源加载完成注册,避免加载失败。
导入插件代码后,在代码文件中,参照文档进行下载器的注册,这里的代码与引擎代码基本一致,只是将下载图片的代码进行了改造。对于图片资源,通过arraybuffer下载后,转换为base64进行显示。
const downloadImage = (url, options, onComplete) => {
const func = cc.sys.hasFeature(cc.sys.Feature.IMAGE_BITMAP) && cc.assetManager.allowImageBitmap ? cc.assetManager.downloader.downloadBlob : downloadImageByBuffer;
func(url, options, onComplete);
}
const downloadImageByBuffer = (url, options, onComplete) => {
console.debug("downloadImageByBuffer:" + url);
downloadArrayBuffer(url, options, function (err, data) {
if (err) {
onComplete && onComplete(null, data);
return;
}
if (checkIsEncripted(data, ENCRYPT_SIGN)) {
let index = url.lastIndexOf(".");
let suffix = url.substr(index + 1);
let typeStr = imgTypes[suffix] || imgTypes["png"];
data = decodeArrayBuffer(data, ENCRYPT_SIGN, ENCRYPT_KEY);
let base64code = arrayBufferToBase64Img(data);
base64code = `data:${typeStr};base64,${base64code}`
cc.assetManager.downloader.downloadDomImage(base64code, options, onComplete);
}
else {
cc.assetManager.downloader.downloadDomImage(url, options, onComplete);
}
})
}
因为引擎对导入的插件代码没有进行加密,因此还需要对插件代码进行混淆则更为安全,提高破解者的成本。这部分内容将在demo中体现。
原生端实现
跟踪代码可发现,在原生端,资源的加载基本都会经过getStringFromFile()与getDataFromFile()方法,因此在这两个方法中进行解密,可以满足大多数的资源解密需求,在C++中进行处理,效率也将更高,对加载的影响也可以降低一些。
至此,简单的加密流程已经结束。
其他的加强内容:
- 1、代码混淆,提高查看解密方法的成本,这一点,3.x提供了脚本加密的方式,也能提高一定的安全性。
- 2、加密密钥不采取本地明文,使用启动时网络获取,提高获取密钥的成本。
- 3、密钥采用多地址存储,提高调试成本。
- 4、图片隐藏水印,保留法律追溯的途径。
- 5、防改包重新上线:启动或不定时进行签名验证,采取上报后台措施或不定时FC。
签名验证参考文章:
Android签名校验_zhu6201976的博客-CSDN博客_android 签名验证
小结
以上是在web/native等不同平台的资源加解密方案,因为加密是在打包时一次完成,因此密钥与加密方法都是固定的,加上运行环境在用户手中,高级破解玩家如果原因耗费大量的时间进行破解,理论上也是可以拿到密钥与解密方法,无法完全防止破解。
但是随着加密手段的加强,能够逐渐增加破解者的破解成本,继而能够劝退很大一部分破解者,本质上是与破解者的“博弈”。
以上是羽毛对资源加密的理解,理解有误的地方欢迎大家纠错与交流。
Demo
加密demo已上架store,开箱即可食用。目前仅支持png资源的加解密,将陆续加入其他资源类型的加密。
web/native资源加密工具与demo v1.0.0
https://store.cocos.com/app/detail/4201
更多
CocosCreator3.4.2原生二次开发的正确姿势——手把手教你接SDK
关于作者
我是羽毛,一名游戏研发工程师,一名野生摄影同学。我的公众号主要分享自己的一些游戏项目开发过程中的功能总结及日常开发笔记。也希望能通过平台的交流,与更多有想法的同学交流认识,共同成长。
欢迎大家在日常开发过程中,如果觉得有需要讨论解决、分享或者探讨的内容,在公众号后台或者文章留言处给我反馈,提供写作的方向,从另一个角度也尽量让写作内容更贴近大家的需求以及痛点,在此谢谢各位同学