一。最近收到一個對js源碼進行加密,以防止關鍵js源代碼洩露的需求!由于我們的項目是基于vue-cli腳手架開發的,是以首先想到的是其中的webpack插件UglifyJsPlugin是否提供js代碼加密的配置。查詢UglifyJsPlugin官方手冊後發現該插件提供代碼壓縮及混淆功能,可以對代碼進行壓縮,變量、函數名替換為簡單字母,降低了源碼的可讀性,一定程度上達到了加密的目的,但是加密級别沒有達到我們的要求。是以隻能親自動手找找是否有現成的開源加密算法,拿過來借用一下。
二。在網上找到了一個名為'jjencode'的js加密算法,這裡貼出他的網站:https://www.sojson.com/jjencode.html,通過此算法對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插件都能實作并且做得更好。
五。引入我們開發的JsEncodePlugin插件後,運作'npm run build'做一個線上打包後,我們就可以檢視加密後的檔案了。