Electron-如何保護源碼?

一開始聽到這個需求挺懵的,做爲一個聊天軟件,代碼裏並無所謂核心算法和商業機密,爲何須要保護源碼。何況Electron自己在打包時提供了asar這種archive文件格式,會將全部源碼和依賴封裝。node

需求

一陣分析後,Electron項目源碼保護仍是有必要的。git

  • asar只是對源碼的合併歸檔,並不提供加密之類的操做。 經過asar e的命令,能夠很簡單地進行解壓和獲得源碼。
  • 業務上,即時通信應用的聊天數據均存儲在本地,雖然使用了加密版的sqlite3。但拿到源碼,也就意味着知道了密鑰,數據庫加密也就形同虛設。

尋找解決方案

asar加密

翻github和Stack Overflow,發現對Electron源碼保護方案討論由來已久。github

Source Code Protection #3041算法

Add Encryption Feature #46sql

總結下來,官方並無打算提供解決方案。做者們認爲,不管用什麼形式去加密打包文件,密鑰總歸是須要放置在包裏的。。shell

繼續翻,國內論壇上一些大佬有嘗試解決過這個問題,是從asar打包這塊切入,然而,並無看懂。。數據庫

electron 加密打包的正確方法json

簡單理解下大佬的思路:對asar源碼進行分析,在 asar 打包時寫入文件以前, 經過加密算法把寫入的文件進行加密;在asar.js讀取文件處添加對應文件解密算法;同時對asar文件頭部 json 進行加密,使得官方的 asar 就無法解包了。canvas

思路我是看懂了,怎麼下手徹底不知。有興趣的童鞋能夠主動去留言詢問。。安全

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 configurenode-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封裝核心業務代碼,能必定程度提高源碼的安全性。但須要修改以前的打包流程,開發調試上也會帶來一些不便。仍是看業務上如何取捨吧。

相關文章
相關標籤/搜索