当前位置: 首页 > Web前端 > HTML

一个在服务器加密3D模型资源的做法

时间:2023-03-28 13:28:03 HTML

最近领导要求防止在线3D模型被下载。查阅了网上关于这个话题的讨论,基本的回答是组件不能下载,只能添加下载的组件。难点,比如这篇文章,https://forum.babylonjs.com/t...。但是领导已经说了,很简单,不就是二进制数据吗,加密一下就可以了,只能先想办法了。目前能想到的方案很简单:服务器返回3D模型数据时,对返回内容进行加密。前端解密返回的内容。由于前端解密有性能需求,可以考虑使用WebAssembly+ServiceWorker来进行解密操作。如果对这两种技术不了解,可以自行查阅相关资料。初始化场景基于babylon.js,导入一个glb格式的模型,基于webpack构建一个简单的工程constcanvas=document.getElementById("canvas")asHTMLCanvasElement;constengine=newEngine(canvas);engine.setSize(window.innerWidth,window.innerHeight);constscene=newScene(engine);scene.createDefaultCameraOrLight(真,true,true);SceneLoader.AppendAsync("/static/models/","Xbot.glb").then((scene)=>{});functionrender(){engine.runRenderLoop(()=>{场景渲染();});}使成为();打开效果如图。使用Node.js加密模块进行服务器端加密。返回数据时,对数据进行加密。首先定义一个keyconstkey=Buffer.from("6b65796b65796b65796b65796b65796b65796b65796b65796b65796b65796b65796b6579","hex").toString("utf8");然后进行指定的资源行加密,这里只以glb文件为例"./public/models/Xbot.glb");console.log("filepath:",filepath);if(!fs.existsSync(filepath)){res.status(404).send("");}else{constcipher=crypto.createCipheriv("aes-192-ctr",key,Buffer.alloc(16,0));constbuf=Buffer.from(filepath);res.setHeader("内容类型","application/octet-stream");fs.createReadStream(buf).pipe(cipher).pipe(res);//fs.createReadStream(buf).pipe(res);}});加密后模型无法打开,见下图返回加密前说明加密有效。不应打开下载的模型。前端解密加入serviceworker相关代码入口文件。先注册if("serviceWorker"innavigator){navigator.serviceWorker.register("/sw.js").then(function(reg){//注册成功console.log("注册成功.Scopeis"+reg.范围);}).catch(函数(错误or){//注册失败console.log("Registrationfailedwith"+error);});}添加一个sw.ts文件,通过sw抓取文件,对数据进行解密self.addEventListener("install",(event)=>{console.log("installing");});self.addEventListener("activate",(event)=>{console.log("activating");});constfinish=()=>{};constdecrypt=(v)=>{};self.addEventListener("fetch",function(event:any){event.respondWith((asyncfunction(){consturl=event.request.url;if(event.request.url.endsWith(".glb")){constresponse=awaitfetch(event.request);if(response.status!==200)returnresponse;constreader=response.body.getReader();conststream=newReadableStream({start(controller){functionpush(){reader.read().then(({done,value})=>{console.log("value:",va卢);如果(完成){控制器。关闭();完成(网址);返回;}控制器。排队(解密(值,网址));推();});}推();},});返回新的响应(流);}else{returnfetch(event.request);}})();});打开控制台查看是否注册成功。解密代码可以用c/c++或者rust等语言,然后编译成wasm,这里使用rust解密lib.rsexterncratewasm_bindgen;externcrateaes_ctr;externcratehex;#[macro_use]externcratelazy_static;useaes_ctr::Aes192Ctr;使用aes_ctr::stream_cipher::generic_array::GenericArray;使用aes_ctr::stream_cipher::{NewStreamCipher、SyncStreamCipher、SyncStreamCipherSeek};使用std::collections::HashMap;使用std::sync::Mutex;使用wasm_bindgen::prelude::*;lazy_static!{staticrefcipherMap:Mutex>>=Mutex::new(HashMap::new());}#[wasm_bindgen]pubfndecrypt(mutbuffer:&mut[u8],key:&str)->Vec{让mutcipherMapLock=cipherMap.lock().unwrap();让stringKey=String::from(key);如果!cipherMapLock.contains_key(&stringKey){让cipherKey=hex::decode("6b65796b65796b65796b65796b65796b65796b65796b6579").unwrap();cipherMapLock.insert(stringKey.to_string(),Mutex::new(Aes192Ctr::new_var(&cipherKey,&[0;16]).unwrap()));}letmutcipher=cipherMapLock.get(&stringKey).unwrap().lock().unwrap();cipher.apply_keystream(&mut缓冲区);buffer[..].to_vec()}#[wasm_bindgen]pubfnfinish(key:&str){cipherMap.lock().unwrap().remove(&String::from(key));()}编译为2个文件lib.jslib.wasm然后引入sw.ts文件importScripts(`/static/wasm/lib.js`);WebAssembly.compileStreaming(fetch(`/static/wasm/lib.wasm`)).then((mod)=>WebAssembly.instantiate(mod,{imports:{}}).then((instance)=>{self.wasm=instance.exports;}));刷新,模型又可以正常加载了,查看控制台解密下载保护成功,但是下载文件的时候也会通过sw解密,还是可以下载分析FetchEvent,发现那个event.request下载时.referrer为空,暂时以此作为下载加载资源的区分标准。修改获取代码为start(controller){functionpush(){reader.read().then(({done,value})=>{console.log("value:",value);if(done){controller.close();finish(url);return;}if(event.request.referrer)controller.enqueue(decrypt(value,url));elsecontroller.enqueue(value);push();});}push();},现在下载glb文件发现模型打不开,网页加载正常。总结本文只涉及glb文件,并没有测试.obj.gltf等资源文件。这个方案目前只是实验,还没有上线使用,不知道效果如何。如果大家有更好的方案,或者对这个方案有什么补充,希望大家可以在评论区补充。本文源码地址