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");
}
});