Electron-如何保護原始碼?
翻github和Stack Overflow,發現對Electron原始碼保護方案討論由來已久。
總結下來,官方並沒有打算提供解決方案。作者們認為,無論用什麼形式去加密打包檔案,金鑰總歸是需要放置在包裡的。。
繼續翻,國內論壇上一些大佬有嘗試解決過這個問題,是從asar打包這塊切入,然而,並沒有看懂。。
簡單理解下大佬的思路:對asar原始碼進行分析,在 asar 打包時寫入檔案之前, 通過加密演算法把寫入的檔案進行加密;在asar.js讀取檔案處新增對應檔案解密演算法;同時對asar檔案頭部 json 進行加密,使得官方的 asar 就沒法解包了。
思路我是看懂了,怎麼下手完全不知。有興趣的童鞋可以主動去留言詢問。。
addons封裝核心程式碼
electron issue裡有人提出可以利用nodejs的addons來封裝核心程式碼。addons是nodejs實現跨平臺呼叫原生程式碼的外掛,因為保護原始碼的主要目的是為了提高安全性,將資料庫金鑰等關鍵欄位儲存在原生程式碼中,提高破解門檻。
實現
C++語法基本忘光了,先實現一個簡單業務練練手:JS傳入使用者資訊物件,C++讀取物件,處理後,返回資料庫對應的金鑰。
Nodejs與C++之間的型別轉換由V8 API提供,具體可參考Node.js 和 C++ 之間的型別轉換 。
// key.cc #include <node.h> namespace key { using v8::FunctionCallbackInfo; using v8::Isolate; using v8::Local; using v8::NewStringType; using v8::Object; using v8::String; using v8::Value; void GetKeys(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); // 判斷js傳遞的引數是否為物件 if (!args[0]->IsObject()) { printf("not Object\n"); } // 新建物件,將cfg和id繫結到物件 Local<String> cfgKey = v8::String::NewFromUtf8(isolate, "testxxx"); Local<Object> keyObj = v8::Object::New(isolate); keyObj->Set(v8::String::NewFromUtf8(isolate, "cfgKey"), cfgKey); // 讀取js傳遞的物件 Local<Object> userObj = Local<Object>::Cast(args[0]); Local<Value> id = userObj->Get(String::NewFromUtf8(isolate, "id")); keyObj->Set(v8::String::NewFromUtf8(isolate, "id"), id); args.GetReturnValue().Set(keyObj); } void GetUidByUserInfo(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); Local<Object> userObj = Local<Object>::Cast(args[0]); Local<Value> id = userObj->Get(String::NewFromUtf8(isolate, "id")); args.GetReturnValue().Set(id); } void Initialize(Local<Object> exports) { NODE_SET_METHOD(exports, "getKey", GetKeys); NODE_SET_METHOD(exports, "getUserKey", GetUidByUserInfo); } NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) } 複製程式碼
key.cc
中暴露了兩個簡單的方法,分別是獲取所有key的物件和獲取單獨使用者的key,當然,這裡只是簡單的業務邏輯展示。在Nodejs Addons中,介面是通過這種模式的初始化函式:
void Initialize(Local<Object> exports); NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) 複製程式碼
NODE_GYP_MODULE_NAME
,是在binding.gyp中設定的模組名稱。Nodejs不能直接呼叫C++檔案,需要先通過node-gyp將其編譯為二進位制檔案,binding.gyp則是類似JSON格式的構建配置檔案。在根目錄下新建該檔案:
{ "targets": [ { "target_name": "dbkey", "sources": [ "key.cc" ] } ] } 複製程式碼
安裝好node-gyp和相關依賴。先後輸入命令node-gyp configure
,node-gyp build
成功後,生成build目錄,得到二進位制檔案dbkey.node。
然後,我們寫個js測試下。
const dbKey = require('./build/Release/dbkey'); const userInfo = { id: '123456', }; console.log(dbKey.getKey()); // { cfgKey: 'testxxx', id: '123456' } console.log(dbKey.getUserKey(userInfo)); // 123456 複製程式碼
通過require(),我們就可以呼叫C++模組。
但此時的dbkey.node並不能直接扔進electron中使用,我們需要用electron相關標頭檔案對該外掛進行重編譯。
node-gyp rebuild --target=1.7.11 --arch=x64 --target_platform=darwin --dist-url=https://atom.io/download/atom-shell
根據你的electron版本號(target)和平臺(target_platform)分別重編譯。
ps. 因為Nodejs版本很多,其V8 API也不完全一致,C++邏輯建議使用NAN ,NAN對V8 API做了封裝,使我們不用關心版本問題。我們專案中使用的Native模組,如canvas,sqlite等,其原始碼也都是使用NAN。
總結
利用C++ Addons封裝核心業務程式碼,能一定程度提升原始碼的安全性。但需要修改之前的打包流程,開發除錯上也會帶來一些不便。還是看業務上如何取捨吧。