【Front-End Developing】jsEncrypt實現前端加密與解密

0x00 前言

感謝並借鑑另外一位博友的經驗,剛好最近乙方在做一些安全性測試的時候提到

在做項目中的登錄功能時一般是通過form表單或者ajax方式將參數提交到服務器進行驗證,在這個過程中,在前端對登錄密碼先進行一次加密的話,安全性肯定要優於直接提交的方式。最近在看博客園的登錄頁面時發現博客園的登錄是用ajax發送http請求的方式,並在前端採用了加密,是採用jsencypt在前端進行加密的。後面查閱資料後瞭解到淘寶、京東也有用jsencypt庫對登錄密碼進行前端加密的操作。

jsencypt具體的使用參考:

0x01 一般使用

創建密鑰對JKS格式keystore:

keytool -genkey -v -alias test -keyalg RSA -keystore test.jks 

將JKS格式keystore轉換成PKCS12證書文件:

keytool -importkeystore -srckeystore test.jks -destkeystore test.p12 -srcstoretype JKS -deststoretype PKCS12

使用OpenSSL工具從PKCS12證書文件導出密鑰對:

openssl pkcs12 -in test.p12 -nocerts -nodes -out test.key 

從密鑰對中提取出公鑰:

openssl rsa -in test.key -pubout -out test_public.pem 

拿到公鑰test_public.pem後,在cat test_public.pem查看這個公鑰內容,內容是base64格式的,這個公鑰就是供在前端用jsencrypt對登錄密碼等參數進行RSA加密用的,看下test_public.pem內容:(這裏複製github上的過來,讀者可以自行嘗試)

—–BEGIN RSA PUBLIC KEY—– 
MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQ 
WMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNR 
aY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB 
AoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fv 
xTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeH 
m7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd 
—–END RSA PUBLIC KEY—– 

接下來用一段簡單的前端代碼演示下jsencrypt的使用:

    <!doctype html><html>
      <head>
        <title>jsencrypt使用</title>
        <script src="./jquery.min.js"></script>
        <script src="./jsencrypt.min.js"></script>
        <script type="text/javascript">
            $(function() {
                $('submit').click(function() {                
                    var data = [];
                    data['username']= $('#username').val();
                    data['passwd']= $('#passwd').val();                
                    var publickey = $('#publickey').val();
                    encryptSend('./Jsencrypt.do', data, publickey);  // Jsencrypt.do對應服務端處理地址
                });
            });        
    
            // 使用jsencrypt庫加密前端參數
            function encryptSend(url, data, publicKey){  
                       **// 最主要的內容**
                ***var jsencrypt = new JSEncrypt();
                jsencrypt.setPublicKey(publicKey);            
                // enData用來裝載加密後的數據
                var enData = new Object();            
                // 將參數用jsencrypt加密後賦給enData
                for(var key in data){
                    enData[key] = jsencrypt.encrypt(data[key]);
                }***
    
                $.ajax({
                    url: url,
                    type: 'post',
                    data: enData,
                    dataType: 'json',
                    success: function (data) {                    
                        console.info(data);
                    },
                    error: function (xhr) {                    
                        console.error('Something went wrong....');
                    }
                });
            }    
        </script>
      </head>
      <body>
        <label for="publickey">Public Key</label><br/>
        <textarea id="publickey" rows="20" cols="60">
            -----BEGIN RSA PUBLIC KEY-----
            MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQ
            WMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNR
            aY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB
            AoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fv
            xTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeH
            m7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd
            -----END RSA PUBLIC KEY-----    
        </textarea>
        <br/>
        <label for="input">jsencrypt:</label><br/>
        name:<input id="username" name="username" type="text"></input><br/>
        password:<input id="passwd" name="passwd" type="password"></input><br/>
        <input id="submit" type="button" value="submit" />
      </body>
    </html>

下面演示服務端解密過程,以Java爲例。

    import java.io.FileInputStream;
    import java.security.KeyStore;
    import javax.crypto.Cipher;
    import org.apache.log4j.Logger;
    import sun.misc.BASE64Decoder;
    
    public class JsencryptTest {    
        private static final Logger logger = Logger.getLogger(JsencryptTest.class);    
        public static void main(String[] args) {        
            byte[] bs = null;        
            try {
                BASE64Decoder decoder = new BASE64Decoder();            
                // encodePwd是前端密碼使用公鑰通過jscencrypt進行加密後得到的(這裏也是複製github上的舉例)
                String encodePwd = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ"
                                    + "DlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6"
                                    + "khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2o"
                                    + "sbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdh"
                                    + "UTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMx"
                                    + "ZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB";
                bs = decoder.decodeBuffer(encodePwd);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            KeyStore keyStore = null;        
            try {
                keyStore = KeyStore.getInstance("JKS");
                keyStore.load(new FileInputStream("D:/jsencrypt/test.jks"), "123456".toCharArray());
                Cipher cipher = Cipher.getInstance("RSA");
                cipher.init(Cipher.DECRYPT_MODE, keyStore.getKey("test", "123456".toCharArray()));
                logger.info(new String(cipher.doFinal(bs)));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

當然在服務端除了上面這樣的處理方式外,也可以根據實際業務場景使用不同的處理方式。

0x02 高安全性使用

下面是我根據自己的業務線來進行的設計流程分析,考慮到高安全(非對稱且前後保持低耦合)的用法,可以這樣處理:

在這裏插入圖片描述

<script language="javascript" type="text/javascript">
dorado.onInit(function(){
try{
var view=new dorado.widget.View({
	"id":"viewMain",
	"name":"bdf2.core.view.frame.Login",
	"listener":{
		"onReady":function(self,arg){
var useCaptcha="true";
var useRemember="true";
function getRealPath(){
	var curPath = window.document.location.href;
	var pathName = window.document.location.pathname;
	var pos = curPath.indexOf(pathName);
	var localhostPath = curPath.substring(0,pos);
	var projectName = pathName.substring(0,pathName.substr(1).indexOf('/')+1);
	var realPath = localhostPath + projectName;
	return realPath;
}
var crypt = new JSEncrypt({default_key_size: 512}).getKey();
var publicKey = crypt.getPublicBaseKeyB64();
var privateKey = crypt.getPrivateBaseKeyB64();

/*
 * 1.傳輸瀏覽器的公鑰到服務器端,服務器返回私鑰加密後的ticket  
 */
$.ajax({
    type: "POST",
    url: getRealPath()+"/ms/manageTicket.bdo",
    data: publicKey,
    async: false,
    cache: false,
    contentType: "application/json",
    success: function (resData, status, response) {
		sessionStorage.setItem("mcusprk", privateKey);
        sessionStorage.setItem("mticket", response.getResponseHeader("ticket"));  //將私鑰傳輸到本地
    }
});

/*
* 2.判空操作
*/
window.checkForm=function(){
	var errorInfo=$("#errorInfo");
	var username=$("#username_").val();
	if(username==""){
		errorInfo.html("用戶名不能爲空!");
		$("#username_").focus();
		return false;
	}
	var password=$("#password_").val();
	if (password == "") {
		errorInfo.html("密碼不能爲空!");
		$("#password_").focus();
		return false;
	}
	var captcha=$("#captcha_").val();
	if(useCaptcha=="true" && captcha==""){
		errorInfo.html("驗證碼不能爲空!");
		$("#captcha_").focus();
		return false;
	}
	return true;
}
/*
*  3.正式加密階段:使用1中返回的ticket對前端json數據包進行加密
*/
$("input[name=btn_Login]").on("click",function(){
	if(checkForm()){
		var decrypt = new JSEncrypt();
        decrypt.setPrivateKey(privateKey);
        var ticket = decrypt.decrypt(sessionStorage.getItem("mticket"));
		var secretKey = CryptoJS.enc.Utf8.parse(ticket);
        var srcs = CryptoJS.enc.Utf8.parse($("#password_").val());
        var encrypted = CryptoJS.AES.encrypt(srcs, secretKey, {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7
        });
        var password = encrypted.toString();
		$("#password_").val(password);
		$("form").submit();
	}
});
$(document).keyup(function(event){
	if(event.keyCode == 13){
		$("input[name=btn_Login]").trigger("click");
	}
});

0x03 參考&致謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章