一、前言
国密即国家密码局认定的国产密码算法。通过自主可控的国产密码算法保护重要数据的安全,是有效提升信息安全保障水平的重要举措。目前,我国在金融、教育、交通、通信、国防工业、能源等各类重要领域的信息系统均已开始进行国产密码算法的升级改造。
现如今对使用国密算法加密的接口进行性能测试也逐渐成为是常见的测试场景。使用 JMeter 希望实现更灵活的国密加密测试方式,可以通过对 JMeter 自定义 Java Sampler进行扩展开发来实现。
二、加密接口
1、什么是SM2
为了保障在金融、医疗、能源等领域保障信息传输安全,国家商用密码管理办公室制定了一系列密码标准,包括 SM1(SCB2)、SM2、SM3、SM4、SM7、SM9、祖冲之密码算法(ZUC)等。其中 SM1、SM4、SM7 是对称算法,SM2、SM9 是非对称算法,SM3 是哈希算法。
国密 SM2 算法是一种先进安全的公钥密码算法,在我们国家商用密码体系中被用来替换 RSA 算法。SM2 算法就是 ECC 椭圆曲线密码机制,但在签名、密钥交换方面不同于 ECDSA、ECDH 等国际标准,而是采取了更为安全的机制。另外,SM2 推荐了一条 256 位的曲线作为标准曲线。
关于非对称算法还要注意几点:
- 公钥是通过私钥产生的。
- 公钥加密,私钥解密是加密的过程。
- 私钥加密,公钥解密是签名的过程。
2、被测接口加密逻辑
- 客户端接口请求数据交互时,需要提前交换加密所需的公钥;
- 在发起请求时,客户端使用提供的公钥对请求参数进行加密;
- 服务端收到后,使用对称私钥进行解密后做业务处理;
- 服务端在数据返回前对返回数据使用客户端提供的公钥加密
- 客户端再接收到数据后使用对称私钥进行解密。
- 加密算法使用国密SM2算法。
数据请求与响应过程的数据加密,需要将原始数据体加密为密文内容,并组装为以下格式:
{
“pt”:”密文”}
三、准备工作
扩展实现 JMeter Java Sampler 之前,先考虑清楚哪些选项需要暴露出来。在使用SM2加密数据发送请求的时候,需要配置一些基本参数。
- 加密文本:客户端请求的 Payload
- 公钥:服务端提供的公钥
下图是本文最终完成的 JMeter自定义 Java Sampler 插件的截图,使用该插件进行测试前,需要输入上面所列的信息。
四、JMeter 扩展实现
步骤1:准备开发环境
本例中将使用 Maven 来管理依赖并进行打包。针对本文的任务, 项目中需要使用到的依赖包括 ApacheJMeter_core 和 ApacheJMeter_java,以及国密SM2相关类库。
<dependencies>
<dependency>
<!-- 本地类库,根据实际项目定制 -->
<groupId>sm-sdk</groupId>
<artifactId>sm-sdk-0.0.9-snapshot.jar</artifactId>
<version>0.0.9-snapshot</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/sm-sdk-0.0.9-snapshot.jar</systemPath>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.69</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
<version>5.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_java</artifactId>
<version>5.6.2</version>
</dependency>
</dependencies>
项目创建完毕后,开始编写代码来实现插件。
步骤2:了解实现方法
实现 Java Sampler 自定义请求的两种方式
- 继承 AbstractJavaSamplerClient 抽象类;
- 实现 JavaSamplerClient 接口。
通过阅读源码可以发现 AbstractJavaSamplerClient 抽象类是 JavaSamplerClient 接口的子类,所以,我们可以新建一个 JavaClass,并继承 AbstractJavaSamplerClient。
AbstractJavaSamplerClient中 默认实现了四个可以覆盖的方法,分别是 getDefaultParameters(), setupTest(), runTest()和 teardownTest()方法。
- 方法1:
SampleResult runTest(JavaSamplerContext context)
性能测试时的线程运行体,实现功能逻辑的主方法,每个线程会循环执行这个方法。runTest 方法定义在接口 JavaSamplerClient 中,扩展协议的主体逻辑就是在这个方法中进行编码实现,是必须要实现的方法。
- 方法2:
public Arguments getDefaultParameters()
主要用于设置传入界面的参数,这个方法由 JMeter 在进行添加 JavaRequest 时第一个运行,它决定了你要在 GUI 中默认显示哪些属性。当每次在 GUI 里点击建立 java requst sampler 的时候会调用该方法。该方法设置了 parameters 的初始值,也可以在 sampler 的 GUI 界面做进一步的修改;
- 方法3、4:
void setupTest(JavaSamplerContext context)
void teardownTest(JavaSamplerContext context)
setupTest 和 teardownTest 顾名思义,就是在 Java 请求开始时候进行的初始化工作,以及结束时候进行的扫尾工作。这两个方法也不是必须要实现的。
步骤3:runTest 方法
先实现 runTest 方法。 runTest 方法的返回结果为 SampleResult,也就是每次“取样“的结果。方法实现的代码结构如下:
/**
* 性能测试线程运行体
* @return
*/
@Override
public SampleResult runTest(JavaSamplerContext context) {
SampleResult result = new SampleResult();
// 设置请求名称
result.setSampleLabel("Jmeter SM2 Java Request");
//开始统计响应时间标记
result.sampleStart();
try {
long begin = System.currentTimeMillis();
log.info("-----------------开始执行加密请求-----------------!");
//调用加密
ciphertext = SMEncry.sm2Encrypt(text,publicKey);
long cost = (System.currentTimeMillis() - begin);
//打印时间戳差值。Java请求响应时间
System.out.println(ciphertext+" 总计花费:["+cost+"ms]");
if (ciphertext == null){
//设置测试结果为fasle
result.setSuccessful(false);
return result;
}
if (ciphertext.length() != 0){
//设置测试结果为true
result.setSuccessful(true);
}else{
//设置测试结果为fasle
result.setSuccessful(false);
result.setResponseMessage("ERROR");
}
}catch (Exception e){
result.setSuccessful(false);
result.setResponseMessage("ERROR");
e.printStackTrace();
}finally {
//结束统计响应时间标记
result.sampleEnd();
log.info("-----------------结束执行加密请求-----------------!");
}
//将请求结果放进result中
result.setResponseData(ciphertext,"UTF-8");
//设置响应结果类型
result.setDataType(SampleResult.TEXT);
//改变查看结果树中显示的名称
result.setSampleLabel("自定义SM2加密请求");
return result;
}
如上所示,代码逻辑主要是:
- 接收入参调用SM2算法加密数据。
- 返回的加密数据结果,给 SampleResult 设置正确的方法、结束时间等,这样 JMeter 引擎可获知测试成功与否,进一步地可以正确显示到 JMeter 的报告结果中。
步骤4:getDefaultParameters 方法
实现参数信息从 JavaSamplerContext 的参数中读取出来:
/**
* 设置传入界面的参数
* @return
*/
@Override
public Arguments getDefaultParameters(){
Arguments arguments = new Arguments();
arguments.addArgument("text","输入报文");
arguments.addArgument("publicKey","输入密钥");
return arguments;
}
参数具体值的输入由脚本编写人员在 JMeter 界面上编辑脚本时指定,或者在运行期间使用指定变量的值。而为了方便脚本编写人员了解并更改所需的参数,我们通过 getDefaultParameters 方法将这些参数在界面上暴露出来。
步骤5:setupTest 方法
实现启动获取参数的操作。
private String text;
private String publicKey;
private String ciphertext;
/**
* 初始化方法
* @return
*/
@Override
public void setupTest(JavaSamplerContext context){
//获取Jmeter中设置的参数
text = context.getParameter("text");
publicKey = context.getParameter("publicKey");
log.info("text is:"+text);
log.info("publicKey is:"+publicKey);
}
五、编译、部署
完成了代码的编写,需要将代码进行编译和部署。这里有两点需要注意:
- 把该项目所依赖的资源打成一个jar,在 target 目录下会需要生成一个 jar 包,编译出来的 jar 包里包含了所需的第三方类库,避免 JMeter 运行时找不到第三方提供的类的问题。
- 由于引入本地类库,需要把此类库也打进 jar 包。
在插件工程新建一个assembly.xml
<assembly>
<id>jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<!-- 默认的配置 -->
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
<!-- 增加scope类型为system的配置 -->
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>system</scope>
</dependencySet>
</dependencySets>
</assembly>
因为本地依赖包scope配置为system,而默认的配置为runtime,所以本地依赖包不会打进去,以上配置主要增加了scope类型为system的配置;这样在打包的时候,就会把本地jar也打包进去
本地类库和assembly.xml
位置,如下图:
接下来配置 pom.xml
。
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.7d.zuozewei.jmeter.TestSM2ByJmeter</mainClass>
</manifest>
<manifestEntries>
<Class-Path>.</Class-Path>
</manifestEntries>
</archive>
<!-- 将这一段注释掉 -->
<!--<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>-->
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<!-- 增加配置 -->
<configuration>
<!-- assembly.xml文件路径 -->
<descriptors>
<descriptor>src/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
最后,打包的效果如下:
将编译好的 jar-with-dependencies 包拷贝到 $JMETER_HOME/lib/ext
目录下,重启 JMeter。启动完毕,添加一个 Java 请求,在类名称下拉列表框中应该就能看到新扩展的类了。如果不存在,请查看一下 lib/ext 目录下是否正确拷贝了 jar 包,也可以查一下 JMeter 的日志,确认没有报出异常。
六、使用
新建一个测试脚本,在测试计划中加入一个线程组,然后添加自定义 Java Sampler。
测试参数:
测试加密结果:
那么,如何在业务中使用?可以通过 JSR223 PostProcessor
获取加密报文,通过变量然后传入正常的业务Sampler。
参考示例如下:
//获取响应信息
log.info("response is: "+prev.getResponseDataAsString());
String responsesmessage = prev.getResponseDataAsString();
vars.put("value",responsesmessage);
七、总结
如本文所示,我们可以通过比较灵活的方式来扩展 JMeter 对国密算法的测试支持,希望能给大家带来帮助。
相关代码: