天天看點

【知識整理】vue-cli腳手架開發項目如何進行js進階加密

一。最近收到一個對js源碼進行加密,以防止關鍵js源代碼洩露的需求!由于我們的項目是基于vue-cli腳手架開發的,是以首先想到的是其中的webpack插件UglifyJsPlugin是否提供js代碼加密的配置。查詢UglifyJsPlugin官方手冊後發現該插件提供代碼壓縮及混淆功能,可以對代碼進行壓縮,變量、函數名替換為簡單字母,降低了源碼的可讀性,一定程度上達到了加密的目的,但是加密級别沒有達到我們的要求。是以隻能親自動手找找是否有現成的開源加密算法,拿過來借用一下。

二。在網上找到了一個名為'jjencode'的js加密算法,這裡貼出他的網站:https://www.sojson.com/jjencode.html,通過此算法對js加密背景代碼如下圖:

【知識整理】vue-cli腳手架開發項目如何進行js進階加密

可以看到加密後已經根本看不出任何js語言的痕迹,更不可能看出其中的具體算法是什麼,這種的加密級别基本已經符合我們的需求了。問題來了,加密算法雖然有了,但是我們不能每次重新打包後都手動加密代碼後覆寫原檔案,這使得加密流程變得繁瑣,如果在每次打包後能夠自動加密就好了。怎麼樣才能實作自動加密呢,這就需要我們手寫一個自動加密的插件了。

三。如果對webpack的插件機制不了解的,可以參考這篇文章:https://juejin.im/entry/5a4cb7906fb9a04500037399。我将插件命名為'JsEncodePlugin',源碼如下:

// 1、js-encode-plugin.js 檔案(webpack的js加密插件)
const fs = require('fs');//node的檔案系統子產品,用于讀寫及操作檔案
const path = require('path');//node提供的一些用于處理檔案路徑的小工具
var chalk = require('chalk')//用于向控制台輸出帶顔色的問題提示
// 2、子產品對外暴露的 js 函數
function JsEncodePlugin(pluginOptions) {
  this.options = pluginOptions;
}
           
// 3、原型定義一個 apply 函數,并注入了 compiler 對象
JsEncodePlugin.prototype.apply = function (compiler) {
  const _this = this;
  // 4、挂載 webpack 事件鈎子(這裡挂載的是 after-emit 事件,在将記憶體中 assets 内容寫到磁盤檔案夾之後觸發的webpack生命周期鈎子)
  compiler.plugin('after-emit', function (compilation, callback) {
      // ... 内部進行自定義的編譯操作
      // 5、操作 compilation 對象的内部資料
      console.log(chalk.cyan('\n jsencode start.\n'))
      var filePath = path.resolve(__dirname, _this.options.assetsPath);//設定需要加密的js檔案路徑,_this.options.assetsPath為插件配置中傳過來的需要加密的js檔案路徑
      filterFile(filePath);
      function filterFile(fp){
        fs.readdir(fp, (err, files)=>{//讀取該檔案路徑
          if(err){
            console.log(chalk.yellow(
              '讀取js檔案夾異常:\n' +
              err.message + '\n'
            ))
            return;
          }
          files.forEach((filename)=>{//周遊該路徑下所有檔案
            if(_this.options.jsReg.test(filename)){//利用正則比對我們要加密的檔案,_this.options.jsReg為插件中傳過來的需要加密的js檔案正則,用以篩選出我們需要加密的js檔案。
              var filedir = path.resolve(fp, filename);
              fs.readFile(filedir, 'utf-8', (err, data)=>{//讀取檔案源碼
                if(err){
                  console.log(chalk.yellow(
                    '讀取js檔案異常:\n' +
                    err.message + '\n'
                  ))
                  return;
                }
		//調用jjencode函數對源碼進行jjencode加密,_this.options.global為插件配置中傳過來的加密使用的全局變量名,将在jjencode函數中作為第一個參數傳入
                let result = jjencode(_this.options.global, data);
                fs.writeFile(filedir, result, (err)=>{//将加密後的代碼寫回檔案中
                  if(err){
                    console.log(chalk.yellow(
                      '寫入加密後的js檔案異常:\n' +
                      err.message + '\n'
                    ))
                    return;
                  }
                  console.log(chalk.cyan('  jsencode complete.\n'))
                })
              })
            }
          })
        })
      }
      //js加密函數
      function jjencode( gv, text )
			{
				var r="";
				var n;
				var t;
				var b=[ "___", "__$", "_$_", "_$$", "$__", "$_$", "$$_", "$$$", "$___", "$__$", "$_$_", "$_$$", "$$__", "$$_$", "$$$_", "$$$$", ];
				var s = "";
				for( var i = 0; i < text.length; i++ ){
					n = text.charCodeAt( i );
					if( n == 0x22 || n == 0x5c ){
						s += "\\\\\\" + text.charAt( i ).toString(16);
					}else if( (0x20 <= n && n <= 0x2f) || (0x3A <= n == 0x40) || ( 0x5b <= n && n <= 0x60 ) || ( 0x7b <= n && n <= 0x7f ) ){
						s += text.charAt( i );
					}else if( (0x30 <= n && n <= 0x39 ) || (0x61 <= n && n <= 0x66 ) ){
						if( s ) r += "\"" + s +"\"+";
						r += gv + "." + b[ n < 0x40 ? n - 0x30 : n - 0x57 ] + "+";
						s="";
					}else if( n == 0x6c ){ // 'l'
						if( s ) r += "\"" + s + "\"+";
						r += "(![]+\"\")[" + gv + "._$_]+";
						s = "";
					}else if( n == 0x6f ){ // 'o'
						if( s ) r += "\"" + s + "\"+";
						r += gv + "._$+";
						s = "";
					}else if( n == 0x74 ){ // 'u'
						if( s ) r += "\"" + s + "\"+";
						r += gv + ".__+";
						s = "";
					}else if( n == 0x75 ){ // 'u'
						if( s ) r += "\"" + s + "\"+";
						r += gv + "._+";
						s = "";
					}else if( n < 128 ){
						if( s ) r += "\"" + s;
						else r += "\"";
						r += "\\\\\"+" + n.toString( 8 ).replace( /[0-7]/g, function(c){ return gv + "."+b[ c ]+"+" } );
						s = "";
					}else{
						if( s ) r += "\"" + s;
						else r += "\"";
						r += "\\\\\"+" + gv + "._+" + n.toString(16).replace( /[0-9a-f]/gi, function(c){ return gv + "."+b[parseInt(c,16)]+"+"} );
						s = "";
					}
				}
				if( s ) r += "\"" + s + "\"+";

				r = 
				gv + "=~[];" + 
				gv + "={___:++" + gv +",$$$$:(![]+\"\")["+gv+"],__$:++"+gv+",$_$_:(![]+\"\")["+gv+"],_$_:++"+
				gv+",$_$$:({}+\"\")["+gv+"],$$_$:("+gv+"["+gv+"]+\"\")["+gv+"],_$$:++"+gv+",$$$_:(!\"\"+\"\")["+
				gv+"],$__:++"+gv+",$_$:++"+gv+",$$__:({}+\"\")["+gv+"],$$_:++"+gv+",$$$:++"+gv+",$___:++"+gv+",$__$:++"+gv+"};"+
				gv+".$_="+
				"("+gv+".$_="+gv+"+\"\")["+gv+".$_$]+"+
				"("+gv+"._$="+gv+".$_["+gv+".__$])+"+
				"("+gv+".$$=("+gv+".$+\"\")["+gv+".__$])+"+
				"((!"+gv+")+\"\")["+gv+"._$$]+"+
				"("+gv+".__="+gv+".$_["+gv+".$$_])+"+
				"("+gv+".$=(!\"\"+\"\")["+gv+".__$])+"+
				"("+gv+"._=(!\"\"+\"\")["+gv+"._$_])+"+
				gv+".$_["+gv+".$_$]+"+
				gv+".__+"+
				gv+"._$+"+
				gv+".$;"+
				gv+".$$="+
				gv+".$+"+
				"(!\"\"+\"\")["+gv+"._$$]+"+
				gv+".__+"+
				gv+"._+"+
				gv+".$+"+
				gv+".$$;"+
				gv+".$=("+gv+".___)["+gv+".$_]["+gv+".$_];"+
				gv+".$("+gv+".$("+gv+".$$+\"\\\"\"+" + r + "\"\\\"\")())();";
				//console.log(r);
				return r;
			}
      // 6、執行 callback 回調
      callback();
  });
};
           
// 暴露 js 函數
module.exports =JsEncodePlugin;
           

四。插件寫好後我們要在webpack的配置檔案webpack.prod.conf.js中引入該插件,該檔案為生産環境中webpack的配置入口。它也依賴于webpack.base.conf.js、utils.js和config/index.js。當運作'npm run build'做線上環境打包時會執行該檔案代碼。我們可以注釋掉其中UglifyJsPlugin插件了,因為UglifyJsPlugin插件壓縮和加密的功能,我們開發的JsEncodePllugin插件都能實作并且做得更好。

【知識整理】vue-cli腳手架開發項目如何進行js進階加密
【知識整理】vue-cli腳手架開發項目如何進行js進階加密

五。引入我們開發的JsEncodePlugin插件後,運作'npm run build'做一個線上打包後,我們就可以檢視加密後的檔案了。

繼續閱讀