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使用说明
- 放置位置:将代码复制到JSR223 Sampler/PreProcessor/PostProcessor中,选择Groovy作为语言
- 密钥替换:将
DECRYPT_SECRET替换为实际的加密密钥 - 加密请求体:在JSR223 PreProcessor中,从JMeter变量获取用户数据(如
vars.get("userName")),组装成Map后调用encryptQuery,将结果存入变量(如vars.put("encryptedBody", encryptedResult)),然后在HTTP请求的请求体中引用该变量 - 解密响应体:在JSR223 PostProcessor中,获取响应内容(
prev.getResponseDataAsString()),调用decryptPostResponse,将解密后的结果存入变量或直接处理 - 依赖说明:JMeter默认已包含BouncyCastle库,无需额外添加依赖
内容的提问来源于stack exchange,提问作者AMIT JAMPALKAR
相关产品推荐
相关产品推荐

