Jmeter+RSA+AES加密接口測試實戰總結
加密接口測試與不加密測試有什么區別
- 相同點
1.都是通過http請求去發送請求,返回數據
2.都需要進行參數組裝構建
- 不同點
1.明文參數需要進行一次AES+Base64加密
2.每個請求均需要生成一個RSA公鑰
3.每次都需要根據服務器給的隨機公鑰對AES秘鑰進行加密。
4.每次都要生成一個RSA私鑰
從抓包結果進行結構化分析
{
"clientPublicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjX+6V/uM29molGfhR3iYMvnKk/Z8g9QDAIAv2nrFGIQBw5owzNi/ip+OX49lJ+VjOIRya/eHNew2lzV/7FjQ9VSE3eBSOrObuGsr3dhuHNv8ry9lCQIEh/2hLHC+7DamhpR3b0gQSu7ZlZIAXy+bG4y5Hf2iXdY/UB/R60SaOUcEiFNgLR7hzPeS4bK4P6k35T6KwlpJiwc0fyqHjMSve0X/QtogjaIhEPBv9TJYNFGa1Fa9+0q0rLwDR1XPwPXkPgJi4mN1uP12KGUDOwNCQJWLfvYL4H+k0jfL6EbmBft9DFLLeycM38+durp0FodffJjGsixNQ2KODR+dhElF2QIDAQAB",
"encryptData": {
"loginName": "LBnZdCXQ0D7RT8Oe9n5Kg21/sd+cJDQ11FF9zqGbKpY=",
"password": "GGNbRoOiA6WH2EvHi9i/KmLmPCnkVaI7JtxH4Vrbdfs=",
"sign": "cTL+iY2iNARUlCYDbLDelvQn8gZ7ahIX/XR9X5pALSfsngmF8FNdLzIZjbMl+1X/1LGHypRB2k0Mg+raEtKW40cKDuqjXK37J0UsNSm4f12iG9YeonXWn7BFvb421/JbYQxs5S902e0TMvhfrYW1buriev6CzAMk7FLmGnFBwpqSXmZKuukBAAq1CntXVzjZ1qCmkQpVj8HKxXJVptQYKt5twVuLasbnba/XU0h9M55SNRrXhLrh6C6i+z5FNZ174Xs5GByYempwBpgYsFh929+IS5Dx63+CLeYke6u45jIQhQsHvbmDm0lqKk6YeUPOblPIPXT1lgJTulDPujLv7N+DVCSsr6emQ9lCQgQW9wuBSu2QvPLj2AFFqv0TRkEmdUBHXjHBnRYiCC8N1Cu95BDmgDda8FT/J3tm/dC7QzTZuEwnIcZzXxaH6fcxsFIkF6xl9nYdMbL6EScQnAT9AGWbjJJfvO93/Tkm9FiuSyfcbiIOjqewnHrzy3UBrYOMIq2N4ZApY73ay2tiyvlVvssH46/YflbciC9wfKF7JfF73kqNZCV8oBYnaO9pgk0okbKxEidUYxBQnEv/7WqSBy2dO2WsqyjfFg26NYcHoiwBpmwnUGt8GWuJz4uVM4jm0g1F2ObAgAE2bkPO/6QsueWQkS5SoDX7iOZ3ftuYJCL/2+nWcbvQ9Ez8B9hcPZpNJ/liAowusudKVQp8ss+LDtUPTZAqzcGQ3hntLcq+nkIN3XJsqmkR4V2o4TMuaDhIXu88PtrRNY5NjTgT2k1uNhEkLBgEyqb/Im9anirHaMS1QSrMUGEAFi+jODUNExMfyEzbY2CXebcEncjTjdrtxiUrVzJb6hg1Ba5N/l53/xzV+bTmmf4blEjj0uLQmUmKnJ+NeqYlfzdjbxL6nez8xb/KJQRLGh5gzZJqZ/Iu0nw="
},
"encryptKey": "R8U1xQBJpnHxV1wbk7e8vvvN3MsK4zGMKEjv0RqlCmaCUOLADHYnQECyPdwPjDXCwJ+f0LCwLpbko8LYOFthNj7GI3RUdSKvZ2TapOaExk5MUXdKuLZ3M9BaGUXgJaovOX5b4NupubDzxJbhUpZTRStEjUp+jv64zZ+uzAZ3kQKdxK1/O3WQKq4UCtSD1Q6ibx9NA66OvH6jEv9EmxzAURDsIVSM3f5xSP7dsGJOqKvMLUPLR8pW+RcKxHltW9CaJVNMvY5LITzfBKRkuFdXTds+yh6/Yg9UYMmKOoccCUhkfiYzyr6KwR2NfMvta/YBEpl+0I7ezYoPwVWt9k5DIw==",
"loginName": "test"
}
- 上面是一個登錄請求的參數,從上面進行分析, 只能看出有哪些參數 但是無法知道參數的值如何進行模擬構建
- 所以如果要進行這樣的接口測試必須要解決參數加密的問題
RSA+AES加密流程學習分析
利用開發提供的相關代碼進行RSA+AES加密流程定制化封裝
下面代碼是核心工具的入口方法
package com.testpro.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.testpro.RSA.RSA_AES_BASE_64;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
/**
* @author Administrator testdevops
* @description APP加密程序
* @create 2019-07-22 11:15
*/
public class App {
RSA_AES_BASE_64 rsa_aes_base_64 = new RSA_AES_BASE_64();
public static final String PUBLICMAPKEY = "publicKey";
public static final String PRIVATEMAPKEY = "privateKey";
/**
* *
*
* @description 獲得最終參數方法重載
* @param ServerPublicKey 服務器RSA公鑰
* @param ClientPublicKey 客戶端RSA公鑰
* @param ClientPrivateKey 客戶端RSA私鑰
* @param params params對象
* @param LoginName 登錄名稱
* @author testdevops
* @return java.lang.String 轉化成功的參數
* @throws
* @since V1.0.0
*/
public String getEncryptString(
String ServerPublicKey,
String ClientPublicKey,
String ClientPrivateKey,
JSONObject params,
String LoginName) {
String sign = EncryUtil.handleRSA(params, ClientPrivateKey);
params.put("sign", sign);
// 隨機生成AES密鑰
// AES加密數據
JSONObject map = JSONObject.parseObject("{}");
for (Map.Entry<String, Object> entry : params.entrySet()) {
map.put(
entry.getKey(),
rsa_aes_base_64.encryptAES(
ConvertUtils.stringToHexString(JSON.toJSONString(entry.getValue())),
rsa_aes_base_64.getAesKey()));
}
// 使用RSA算法將商戶自己隨機生成的AESkey加密
String encryptkey = null;
try {
encryptkey = rsa_aes_base_64.encryptRSA(rsa_aes_base_64.getAesKey(), ServerPublicKey);
} catch (Exception e) {
e.printStackTrace();
}
JSONObject jo = JSONObject.parseObject("{}");
jo.put("encryptData", map);
jo.put("encryptKey", encryptkey);
jo.put("clientPublicKey", ClientPublicKey);
jo.put("loginName", LoginName);
return jo.toJSONString();
}
/**
* *
*
* @description 獲得最終參數方法重載
* @param ServerPublicKey 服務器RSA公鑰
* @param jsonObject 參數列表對象
* @param LoginName 登錄名稱
* @author testdevops
* @return java.lang.String
* @throws
* @since V1.0.0
*/
public String getEncryptString(
String ServerPublicKey, JSONObject jsonObject, String LoginName) {
String ClientPublicKey = "";
String ClientPrivateKey = "";
try {
final Map<String, String> generateKeyPairMap = rsa_aes_base_64.generateKeyPair();
ClientPublicKey = generateKeyPairMap.get(PUBLICMAPKEY);
ClientPrivateKey = generateKeyPairMap.get(PRIVATEMAPKEY);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return getEncryptString(
ServerPublicKey, ClientPublicKey, ClientPrivateKey, jsonObject, LoginName);
}
/**
* *
*
* @description 獲得最終參數方法建議方法
* @param ServerPublicKey 服務器RSA公鑰
* @param map 參數列表對象Map
* @param LoginName 登錄名稱
* @author testdevops
* @return java.lang.String
* @throws
* @since V1.0.0
*/
public String getEncryptString(
String ServerPublicKey, TreeMap<String, Object> map, String LoginName) {
final JSONObject jsonObject1 = new JSONObject(map);
return getEncryptString(ServerPublicKey, jsonObject1, LoginName);
}
}
將封裝好的方法打成Jar包使得能在Jmeter中可以使用
<!-- 配置assembly插件實現將所有依賴打成jar包-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>
jar-with-dependencies
</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
在Jmeter中使用封裝好的加密程序執行請求
-
將封裝好的jar拷貝至…\apache-jmeter-5.1\lib\ext目錄下
-
打開jmeter軟件,編寫請求前置處理器JSR223 預處理程序(不要勾選緩存編譯腳本)
import com.testpro.util.App; import java.util.TreeMap; log.info(vars.get("serverkey")); String serverykey =vars.get("serverkey"); //Jmeter Map 不支持泛型 TreeMap params = new TreeMap(); params.put("loginName", "${username}"); params.put("password", "${password}"); log.info(vars.get("username")); log.info(vars.get("password")); String result=new App().getEncryptString(serverykey,params,vars.get("username")); vars.put("loginName",vars.get("username")); log.info(result); vars.put("result",result);//工具返回的參數放入jmeter變量環境中可使用${result}直接引用到該值
-
此時在請求執行前我們就能將參數轉化成秘文方式結果,當做接口的參數發送出去;
-
運行后查看相關發送請求參數情況
//請求數據
POST http://10.5.4.143:8080/xxxx/xxxxx/getValidate.mvc
POST data:
{"clientPublicKey":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu73+o001/rvv/hHTkyp/3SzpW4Cz/xKJbTOjftMMieSqz4b02180gnJVg811grKMXfv1y4X1CgRhiQqOjjH1kJXetba8svr/NHQ8gmB0yzf9oR/gnatLiupUrwQIlSHBwV9E6wjNiGeAOKx2qTTuCSNG79paHxKEud7v/jMf2n3G6nnNA4zF12qYqTXbsNBYOwAlfFKCD89kL/kqWTGCtI2hfoEZNn4Dqpif6JdF2MgpCYQ8DAfBIS4KwhmMZza4PE8pvKywWoFh6PcrGFZ3igf75yfCLrDtFNwGxnbbH5w2cvH9vBSNybC/Tae/GiKDD+/VfefmqZiLOfPXKY6tEQIDAQAB","encryptData":{"password":"U3+V0TxoVWmXg32L67Ez62IWWzBglTUktHU/jp4uKWc=","loginName":"1y1mjaZU5FmaPc+nTvAwueuuwgitHHjTkr51JYEIqmw=","sign":"9rMJIFMMJ5OwGLrlUZDtZRzkVQ9sYFR1IBqTaEk7e49fbM8+zNINjQEizcCYrqR7rzoTCofDhtQmUF+CG9Bih2n94S2dpIq5nO3NJi54G1DSgUA2rfwBjrB0yDqXoF7dR7tYV+kMxXXbf7148O/NCLUaJ3MZWgcx69ok/FgbppbeTxtrKd09O12olSAkQd89elvxbRS0zAV0B+zIZmyE/WR3uwUTVbVq8QQbdfLbQSD0z2/m5pZpbXO+E1Z/m/IIETYoy3tXZi8W6Cq22Z+JF5w+WKOSQo8aUYJrXutBdmSp0b3pboI3B12ZgyDadS4TqBRY+mxdiCtuZlx9nRGhOVONBZD95dTltMRJhIwz4FR6y8ER/dMV8vG1wCGZKHh7ek+4a4iovlbfnbCvVEkG7qLiweIf1GNh5kLP01zAUNmIz2jCbljaJMlKFTMfFXXjoc7ZyGNyonpgh206NUqyvKONReb6Impmi8XCLmdJERzmABVwBTrQJo75SWnjc29vHWdSwdmh1rFodUn8UaCM373buXpgFlFZi3QM5Heig4/hJODOhfTE7IF8xqS/ezsBQ66BBxVC66ux/WJo631XLOz2C9mpcAjVbRBmMsbq78US/MsVNTOa7YM83/5N8CqzX2x0PWLgncLk4esf+xQeNxVHJHvHtDKCUR4OAPHVi3lxV5suTY/REXxyScb9TpnTJdV4LhP9f4fpCmRjqF07WltwKBp6OMr/u4ZSvr/Hex8bKoU2w1gHCgYtF134rQeNjoRWb1DXkW28bix3vlMmaBwOyzKftrzH+WfVqdrWhp2GNJSIHSpmjg/caOc7LvVFKhT7gczTrCi9bO1ONuwvBeGXkBJvmNqCop8ce91L4+L2qeT/1Q2CaIjSEnjFoQSxMCfCwfj1W2l0ER9E4vrLBjojZiKJnIoehtU9+dVSKeI="},"loginName":"zhangju","encryptKey":"b7btnrQExPjfm+upG/M71GM5R1s+VND192gVVhX+V4u0mLr7Jz6XLeRGTcQLbgVBd9m5M66VtfR7al+JNr6pQp0HlA2EDM9mWUPIbaCal8PbiirmDizHMXBJZxcBb9nH5Cmghhejj0TRmnzpTLTHeb29woQRHKBmoZh4F+8T1D4DGkPwau+1b4/qOwoH6NyxV+ffhFRqZvgs8VRDEeOFsMpPVRcx/NxdWVW8lOTJwP7dIW00qSxjm1LxawgHA0JCFggrLt/Xsw+7P7tNG8R5Zb0TjLXC4warl1MDQi/9krCpeOg7FdUKOUMNHqsOsiPaoz4OKiAIMFrqH2/oKIGwkQ=="}
[no cookies]
//服務器返回數據
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=24E521BB395E5A9A9B0CBA5DD21314DE; Path=/xxxx/; HttpOnly
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POTS, GET
Access-Control-Max-Age: 3600
Access-Control-Allow-Headers: x-requested-with,Authorization
Access-Control-Allow-Credentials: true
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 24 Jul 2019 02:34:27 GMT
{
"code": "1",
"data": {
"limit": {
"role": "R2",
"permission": [
{
"data": "D002",
"ope": "C001",
"modalName": "JCBJ",
"page": "0000"
},
{
"data": "0000",
"ope": "0000",
"modalName": "PGBG",
"page": "0000"
},
{
"data": "0000",
"ope": "0000",
"modalName": "SPXC",
"page": "0000"
},
{
"data": "0000",
"ope": "0000",
"modalName": "CZCX",
"page": "0000"
},
{
"data": "9999",
"ope": "9999",
"modalName": "JCXJC",
"page": "9999"
}
]
},
"cellphone": "18555695260",
"telephone": "1775510yfyc4020",
"principalId": "8a85840f6bb1d93a016bb6f76b1a18b6",
"principalName": "張三",
"userName": "張三",
"userId": "8a85840f6bb1d93a016bb6f76b1a18b6",
"email": "dyxy",
"token": "2c54748384fe592560cdb1f02f6f6ad5"
},
"dataType": "",
"message": "請求成功",
"status": "",
"token": ""
}
總結
-
加密請求本身不可怕,可怕的是沒有足夠的勇氣去解決
-
問題不可能一下子解決,但是慢慢總會解決。
https://blog.csdn.net/waynibear/article/details/78084465?locationNum=6&fps=1
################################################################
Jmeter提供了JSR223 PreProcessor前置處理器,該工具融合了Java 8 Nashorn 腳本引擎,可以執行js腳本以便對腳本進行前置處理。其中比較典型的應用就是通過執行js腳本對前端數據進行rsa加密,如登錄密碼加密。
Jmeter提供了JSR223 PreProcessor前置處理器,通過該工具融合了Java 8 Nashorn 腳本引擎,可以執行js腳本以便對腳本進行前置處理。其中比較典型的應用就是通過執行js腳本對前端數據進行rsa加密,如登錄密碼加密。
rsa加密方式形如(用到了security.js這個腳本):

jQuery.ajax({
type:"post",
url:"loginset",
success:function(rd){
if(rd!=null){
//加密模
var Modulus = rd.split(';')[0];
//公鑰指數
var public_exponent = rd.split(';')[1];
//通過模和公鑰參數獲取公鑰
var key = new RSAUtils.getKeyPair(private_exponent, "", Modulus);
//顛倒密碼的順序,要不然后解密后會發現密碼順序是反的
var reversedPwd = password.split("").reverse().join("");
//對密碼進行加密傳輸
var encrypedPwd = RSAUtils.encryptedString(key,reversedPwd);
jQuery('#subPwd').val(encrypedPwd);
jQuery('#loginPwd').val("");
jQuery('#login').submit();
}
}
type:"post",
url:"loginset",
success:function(rd){
if(rd!=null){
//加密模
var Modulus = rd.split(';')[0];
//公鑰指數
var public_exponent = rd.split(';')[1];
//通過模和公鑰參數獲取公鑰
var key = new RSAUtils.getKeyPair(private_exponent, "", Modulus);
//顛倒密碼的順序,要不然后解密后會發現密碼順序是反的
var reversedPwd = password.split("").reverse().join("");
//對密碼進行加密傳輸
var encrypedPwd = RSAUtils.encryptedString(key,reversedPwd);
jQuery('#subPwd').val(encrypedPwd);
jQuery('#loginPwd').val("");
jQuery('#login').submit();
}
}
如何在
jmeter
中執行這個js,獲得加密后的串呢?


首先調用請求獲取公鑰所需的參數(exponent、modulus
)
通過正則表達式提取到參數后,作為參數傳遞到JSR223前置處理器中,生成公鑰,然后再對登錄密碼進行加密。
JSR223前置處理器及寫出的腳本如下圖


這里exponent、modulus、passwd應該是在上一請求中獲取后作為變量傳入,這里簡化,直接給定,
最后將加密后的字符串輸出到變量data中,以供后續使用。
load("security.js");
print("starttest");
function RSA(){
var exponent="10001";
var modulus="eba486abd41cc0950eae9972f58f43c62bba871660b86905cebbbbffcac137915744a2d37c25a8915562343602761293297baf84386da8ab7e847338f4b0aa347bfd847c55319d18efc0d80286509fd5a73bd182d97f3949efdc070e103c89639a415b8e579628d2d182b8a1b544889ae364c43cae42b1c53b423514c9973d67";
var passwd="1234";
var publicKey = new RSAUtils.getKeyPair(exponent, '', modulus);
return RSAUtils.encryptedString(publicKey, passwd);
}
var data = RSA();
log.info(data);
vars.put("Password",data);
在執行過程中遇到幾個問題,
1、 Problem in JSR223 script JSR223 PreProcessor javax.script.ScriptException: ReferenceError: "window" is not defined in security.js at line number 10
at jdk.nashorn.api.scripting.NashornScriptEngine.throwAsScriptException(NashornScriptEngine.java:470)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:454)
解決辦法:報window未定義,window為security.js中引入,為瀏覽器執行js執行的全局對象。
此問題排查了很久,網上也未有說明,最后找到是
Nashorn JS腳本引擎並不支持瀏覽器的這些對象,需要對js腳本進行改造。后來檢索
JavaScript 對象,了解到在頂層 JavaScript 代碼中,可以用關鍵字 this 引用全局對象。將js末尾的window修改為this后未再報錯。


2、在前置處理器中引用js腳本,但是實際腳本不運行也不報錯

解決辦法:需要將js腳本放到jemete bin目錄下調用,將前置處理器中的文件選擇清空。執行成功,不報錯。

3、調試時可用 log.info(data);或print("starttest");進行打印輸出。
4、要加密的字符串需要反序處理,否則解密后字符串是反的。(此問題與加密js有關)
var
reversedPwd = password.split(
""
).reverse().join(
""
);