cocos creator资源加密(支持web和native)

上期文章研究了资源加载流程,资源加载流程中,读取资源数据是在download过程中,如果我们要对资源进行解密,要改的就是这里了。

我做了一个加密插件:
image

支持对web平台和native平台进行加密,支持对文件名进行混淆。

上篇文章讲的都是web平台的流程,对于native平台,下载流程是不一样的,使用的是c++端导出的多线程下载,适配代码在jsb-engine.js中:
image
c++端读取文件全都是通过FileUtils::getStringFromFile或者FileUtils::getDataFromFile进行操作,我们解密统一在这两个函数里进行就可以了。

对于web平台的解密,正常情况下,应该通过cc.assetManager.downloader.register注册不同的下载方法。但是为了不破坏源工程,我选择修改构建后导出的cocos2d-js.js文件,主要是修改有三点,解密代码放在downloadArrayBuffer,修改downloadText和downloadImage,改为通过downloadArrayBuffer先下载二进制流再解密解码为文本或图片。

var downloadArrayBuffer = function downloadArrayBuffer(url, options, onComplete) {
    options.responseType = "arraybuffer";
    url = _getRealPath(url)
    downloadFile(url, options, options.onFileProgress, function (err,data) {
        if(!err){
            ///解密
            data = hyz._decriptTool.decodeArrayBuffer(data);
        }
        onComplete&&onComplete(err,data)
    });
};
var downloadText = function downloadText (url, options, onComplete) {
    downloadArrayBuffer(url,options,function (err,data) {
    if(err){
        onComplete&&onComplete(err,data)
    }else{
        ///转化成Text
        hyz.arrayBuffer2Text(data,function(err,text) {
        if(err){
            onComplete&&onComplete(err,text)
        }else{
            onComplete&&onComplete(null,text)
        }
        })
    }
    })
};
var downloadJson = function downloadJson(url, options, onComplete) {
    downloadText(url,options,function (err,data) {
    if(err){
        onComplete&&onComplete(err,data)
        return;
    }
    if (!err && typeof data === 'string') {
        try {
            data = JSON.parse(data);
        }
        catch (e) {
            err = e;
        }
    }
    onComplete && onComplete(err, data);
    })
};
var downloadImage = function downloadImage(url, options, onComplete) {
    downloadArrayBuffer(url,options,function(err, data){
    if(err){
        onComplete&&onComplete(null,data);
        return;
    }
    let index = url.lastIndexOf(".");
    let suffix = url.substr(index+1);
    let typeStr = hyz.imgTypes[suffix]||hyz.imgTypes["png"]

    if(cc.sys.capabilities.imageBitmap){
        let blob = new Blob([data],{type:typeStr})
        onComplete&&onComplete(null,blob);
        cc.log(blob)
    }else{
        let base64code = hyz.arrayBufferToBase64Img(data);
        base64code = `data:${typeStr};base64,${base64code}`
        downloadDomImage(base64code,options,onComplete)
    }
    })
};

如上,主要是downloadArrayBuffer进行解密,另一个要注意的就是下载图片要手动转化一下。

加密解密使用的最简单的异或加密,代码如下:

encodeArrayBuffer(arrbuf,sign=this.encriptSign,key=this.encriptKey) {
        if(this.checkIsEncripted(arrbuf,sign)){
            return
        }
        let signBuf = new Uint8Array(this.strToBytes(sign));
        let keyBytes = this.strToBytes(key)
        let buffer = new Uint8Array(arrbuf);
        
        let _outArrBuf = new ArrayBuffer(signBuf.length+buffer.length)
        let outBuffer = new Uint8Array(_outArrBuf)
        for(let i=0;i<signBuf.length;i++){
            outBuffer[i] = signBuf[i]
        }
        let idx = 0;
    
        for(let i=0;i<buffer.length;i++){
            let b = buffer[i];
            let eb = b^keyBytes[idx]
            if(++idx>=keyBytes.length){
                idx = 0
            }
            outBuffer[signBuf.length+i] = eb
        }
        
        return outBuffer;
    }
    
    decodeArrayBuffer(arrbuf,sign=this.encriptSign,key=this.encriptKey){
        if(!this.checkIsEncripted(arrbuf,sign)){
            return arrbuf;
        }
        let signBuf = new Uint8Array(this.strToBytes(sign));
        let keyBytes = this.strToBytes(key);
        let buffer = new Uint8Array(arrbuf);
    
        let size = buffer.length-signBuf.length;
        let _outArrBuf = new ArrayBuffer(size)
        let outBuffer = new Uint8Array(_outArrBuf)
        let idx = 0;
        for(let i=0;i<size;i++){
            let b = buffer[signBuf.length+i];
            let db = b^keyBytes[idx]
            if(++idx>=keyBytes.length){
                idx = 0
            }
            outBuffer[i] = db;
        }
    
        return outBuffer;
    }

至于文件名混淆,就是对源uuid格式的文件名和一个混淆签名再次进行md5,得到新文件名。
如aaaa-bbbb-cccc-dddd.png变为1111-2222-3333-4444.png
关键是工程中如何识别新文件名。项目运行时,查找文件是根据文件路径查到uuid再组装成url的,我们只需要在url组装后,再按照我们的混淆规则,再算一次就好了,保险起见,只混淆uuid格式的文件名。native模式下,修改jsb-engine.js的transformUrl,对本地url进行再次转换;web模式下,downloadArrayBuffer里直接在下载之前将url转换成混淆后的文件名。

function _getRealPath(path) {
	let excludeChangeNameList = [".mp3",".ogg",".wav",".js",".jsc",]
	if(path.indexOf("assets")!=0){
		return path
	}
  if(!true){//tag
    return path;
  }
	for(let ext of excludeChangeNameList){
	  if(path.endsWith(ext)){
		  return path
	  }
	}
	var ext = path.substr(path.lastIndexOf("."));
	var arr = path.split('/');
	let name = arr[arr.length-1];
	let realPath = path;

	if(name[8]=="-"&&name[13]=="-"&&name[18]=="-"&&name[23]=="-"){
		let md5 = hyz.str_to_md5(name+"ggg5675")
		let arr2 = [8,13,18,23]
		for(let i = arr2.length-1;i>=0;i--){
		  let idx = arr2[i];
		  md5 = md5.slice(0, idx) + "-" + md5.slice(idx);
		}
		md5+=ext;
	
	  realPath = path.replace(name,md5);
	  realPath = realPath.replace("/"+name.slice(0,2)+"/","/"+md5.slice(0,2)+"/");
	  realPath = realPath.replace("\\"+name.slice(0,2)+"\\","\\"+md5.slice(0,2)+"\\");
	}

	return realPath
};

github地址 欢迎star

28赞

赞赞赞,先mark后看

你好
我这边构建后 是cocos2d-js-min.js
虽然我在 apply-web.js 更改了路径 但是 加密后 页面无法打开了
因为 替换代码 是基于 cocos2d-js 的,这个文件被min后 替换代码就失效了

这个问题解决了,git更新一下

什么时候出个小游戏加密版本啊?期待。。

先来膜拜一波

首先先来个赞,其次web端加密后,在PC和Android设备上正常运行,在IOS上报错了

插件在使用的时候报错

mark!

原生端加密之后崩溃

2.4.8 Android加密测试ok,感谢分享!

大佬,有个地方我搞不懂,在downloadImage方法中downloadArrayBuffer的回调里,调用了downloadDomImage方法,该方法第一个参数本来是个url,但现在传了整个图片文件的文本过去,为什么不报错呢?(我那么写会报错,这是我的代码PluginDecrypt.zip (1.3 KB) )

战术mark

原生端加密之后崩溃(spine动画无效)

spine构建后生成的bin文件加密后解密失败导致异常崩溃,目前过滤掉bin文件加密正常

请教一下,我在web端进行解密代码注入的时候,这个downLoadFile无法识别嘞

这问题解决没?

大佬您好,我加密/jsb-default模式发现资源加载不出来native下的

mark!!! 666