上期文章研究了资源加载流程,资源加载流程中,读取资源数据是在download过程中,如果我们要对资源进行解密,要改的就是这里了。
我做了一个加密插件:
支持对web平台和native平台进行加密,支持对文件名进行混淆。
上篇文章讲的都是web平台的流程,对于native平台,下载流程是不一样的,使用的是c++端导出的多线程下载,适配代码在jsb-engine.js中: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