Jmeter使用封装jar解决RSA+AES参数加密接口请求


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


如何在 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( "" );

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM