You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JMeter加密解密:将JavaScript代码转为可用Groovy代码

JMeter适配CryptoJS AES+Base64加密解密的Groovy实现

录制Web应用的JMeter脚本时,发现请求体和响应体均采用加密格式,需要实现对应加密解密逻辑以支持多用户场景。原开发提供了JavaScript版本的CryptoJS实现,但通过AI转换的Groovy代码无法正常运行,以下是可在JMeter中直接使用的Groovy版本代码,完全对齐原JS逻辑。

完整Groovy代码

import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import org.bouncycastle.crypto.digests.MD5Digest
import org.bouncycastle.crypto.engines.AESEngine
import org.bouncycastle.crypto.modes.CBCBlockCipher
import org.bouncycastle.crypto.paddings.PKCS7Padding
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher
import org.bouncycastle.crypto.params.KeyParameter
import org.bouncycastle.crypto.params.ParametersWithIV
import org.bouncycastle.util.encoders.Base64
import java.util.Arrays

// 实现CryptoJS兼容的EVP_BytesToKey密钥派生算法
def generateKeyAndIv(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password) {
    def key = new byte[keyLength]
    def iv = new byte[ivLength]
    def digest = new MD5Digest()
    def data = new byte[password.length + (salt != null ? salt.length : 0)]
    
    System.arraycopy(password, 0, data, 0, password.length)
    if (salt != null) {
        System.arraycopy(salt, 0, data, password.length, salt.length)
    }
    
    digest.update(data, 0, data.length)
    digest.doFinal(key, 0)
    
    if (iterations > 1) {
        for (int i = 1; i < iterations; i++) {
            digest.reset()
            digest.update(key, 0, key.length)
            digest.update(data, 0, data.length)
            digest.doFinal(key, 0)
        }
    }
    
    if (ivLength > 0) {
        digest.reset()
        digest.update(key, 0, key.length)
        digest.update(data, 0, data.length)
        digest.doFinal(iv, 0)
        
        if (iterations > 1) {
            for (int i = 1; i < iterations; i++) {
                digest.reset()
                digest.update(iv, 0, iv.length)
                digest.update(data, 0, data.length)
                digest.doFinal(iv, 0)
            }
        }
    }
    return [key, iv]
}

// 加密函数:对齐原JS encryptQuery逻辑
def encryptQuery(Object val, String secret) {
    // 1. 将对象转为JSON字符串
    def jsonStr = new JsonBuilder(val).toString()
    def jsonBytes = jsonStr.getBytes("UTF-8")
    
    // 2. 生成8字节随机盐(CryptoJS默认行为)
    def salt = new byte[8]
    new Random().nextBytes(salt)
    
    // 3. 派生AES密钥和IV
    def keyAndIv = generateKeyAndIv(32, 16, 1, salt, secret.getBytes("UTF-8"))
    def key = keyAndIv[0]
    def iv = keyAndIv[1]
    
    // 4. AES-CBC-PKCS7加密
    def cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding())
    cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv))
    
    def output = new byte[cipher.getOutputSize(jsonBytes.length)]
    def len = cipher.processBytes(jsonBytes, 0, jsonBytes.length, output, 0)
    len += cipher.doFinal(output, len)
    def encryptedBytes = Arrays.copyOf(output, len)
    
    // 5. 拼接Salted__前缀+盐+加密数据,转Base64得到encJson
    def saltedPrefix = "Salted__".getBytes("UTF-8")
    def encJsonBytes = new byte[saltedPrefix.length + salt.length + encryptedBytes.length]
    System.arraycopy(saltedPrefix, 0, encJsonBytes, 0, saltedPrefix.length)
    System.arraycopy(salt, 0, encJsonBytes, saltedPrefix.length, salt.length)
    System.arraycopy(encryptedBytes, 0, encJsonBytes, saltedPrefix.length + salt.length, encryptedBytes.length)
    def encJson = Base64.toBase64String(encJsonBytes)
    
    // 6. 将encJson转UTF-8字节后再次Base64编码,得到最终加密结果
    return Base64.toBase64String(encJson.getBytes("UTF-8"))
}

// 解密函数:对齐原JS decryptPostResponse逻辑
def decryptPostResponse(String val, String secret) {
    // 1. 解码第一层Base64,得到encJson字符串
    def encJsonBytes = Base64.decode(val)
    def encJson = new String(encJsonBytes, "UTF-8")
    
    // 2. 解码encJson的Base64,得到带Salted__前缀的加密数据
    def saltedBytes = Base64.decode(encJson)
    if (saltedBytes.length < 16 || !new String(saltedBytes, 0, 8, "UTF-8").equals("Salted__")) {
        throw new IllegalArgumentException("无效的加密数据格式")
    }
    
    // 3. 提取盐和加密内容
    def salt = Arrays.copyOfRange(saltedBytes, 8, 16)
    def encryptedBytes = Arrays.copyOfRange(saltedBytes, 16, saltedBytes.length)
    
    // 4. 派生密钥和IV
    def keyAndIv = generateKeyAndIv(32, 16, 1, salt, secret.getBytes("UTF-8"))
    def key = keyAndIv[0]
    def iv = keyAndIv[1]
    
    // 5. AES-CBC-PKCS7解密
    def cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding())
    cipher.init(false, new ParametersWithIV(new KeyParameter(key), iv))
    
    def output = new byte[cipher.getOutputSize(encryptedBytes.length)]
    def len = cipher.processBytes(encryptedBytes, 0, encryptedBytes.length, output, 0)
    len += cipher.doFinal(output, len)
    def decryptedBytes = Arrays.copyOf(output, len)
    
    // 6. 解析JSON字符串为对象
    def jsonStr = new String(decryptedBytes, "UTF-8")
    return new JsonSlurper().parseText(jsonStr)
}

// 测试用例
def DECRYPT_SECRET = "Your_Secret_Key"
def testVal = [
    userName: "Test1",
    password: "Test@12345",
    userType: "user"
]

def encryptedResult = encryptQuery(testVal, DECRYPT_SECRET)
println("加密结果: " + encryptedResult)

def decryptedResult = decryptPostResponse(encryptedResult, DECRYPT_SECRET)
println("解密结果: " + decryptedResult)

JMeter使用说明

  1. 放置位置:将代码复制到JSR223 Sampler/PreProcessor/PostProcessor中,选择Groovy作为语言
  2. 密钥替换:将DECRYPT_SECRET替换为实际的加密密钥
  3. 加密请求体:在JSR223 PreProcessor中,从JMeter变量获取用户数据(如vars.get("userName")),组装成Map后调用encryptQuery,将结果存入变量(如vars.put("encryptedBody", encryptedResult)),然后在HTTP请求的请求体中引用该变量
  4. 解密响应体:在JSR223 PostProcessor中,获取响应内容(prev.getResponseDataAsString()),调用decryptPostResponse,将解密后的结果存入变量或直接处理
  5. 依赖说明:JMeter默认已包含BouncyCastle库,无需额外添加依赖

内容的提问来源于stack exchange,提问作者AMIT JAMPALKAR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 03:14:55