1,使用背景
最近工作中对公司接口进行抓包,发现接口路径和返回都是经过加密的,对于查看接口路径及接口返回结果带来了不便,于是想到了对Charles进行小改造,在Charles上增加一个按钮对加密的请求、响应结果解密,本质是执行一个Java方法,然后将解密结果通过文本框显示出来,效果如下
解密前:
点击解密:
解密后:
2,相关环境
Charles版本:4.6.2,界面是基于JDK11版本开发的
JDK版本:所以一定要用jdk11,不然会有坑
3,开发过程
3.1,新建普通工程Custom-Decrypt,引入Charles.jar
3.2,新建CustomDecrypt类,TransactionViewerPopupMenu类,代码如下
CustomDecrypt类中decrypt方法是解密方法,根据需要编写,代码注重实现可能有点low
package com.xk72.charles.gui.transaction.actions;
import com.xk72.charles.model.Transaction;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.spec.KeySpec;
import java.util.logging.Logger;
/**
* @author: create by libin
* @date:2023/4/9
*/
public class CustomDecrypt extends AbstractAction {
public final Transaction transaction;
private static final Logger logger = Logger.getLogger("CustomDecryptLog");
public CustomDecrypt(Transaction transaction) {
super("CustomDecrypt");
this.transaction = transaction;
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
String requestString = transaction.getDecodedRequestBodyAsString();
String responseString = transaction.getDecodedResponseBodyAsString();
logger.warning("__________________________________");
logger.info("request" + requestString);
logger.info("response" + responseString);
logger.warning("__________________________________");
try {
String request;
if (requestString == null || requestString.length() < 1) {
request = "空";
} else {
request = decrypt(requestString, DES_REQUEST_KEY);
}
String respnse;
if (responseString == null || responseString.length() < 1) {
respnse = "空";
} else {
respnse = decrypt(responseString, DES_RESPONSE_KEY);
}
String method = transaction.getMethod();
String host = transaction.getHost() + ":" + transaction.getPort();
String path = transaction.getPath();
String file = transaction.getFile();
String query = transaction.getQuery();
String content = "method:" + method + "\n" +
"host:" + host + "\n" +
"path:" + path + "\n" +
"file:" + file + "\n" +
"query:" + query + "\n";
if ("GET".equals(method)) {
String url = host + file;
content += "url:" + url + "\n";
}
WaringDialog("request", content);
WaringDialog("response", respnse);
} catch (Exception e) {
logger.warning("CustomDecrypt Exception" + e.getMessage());
}
}
public static void WaringDialog(String title, String content) {
JFrame JFrame = new JFrame(title);
JFrame.setPreferredSize(new Dimension(800, 500));
JTextArea textArea = new JTextArea();
textArea.setText(content + "\n");
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
JScrollPane jScrollPane = new JScrollPane(textArea);
jScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
jScrollPane.setAutoscrolls(false);
JFrame.setContentPane(jScrollPane);
JFrame.pack();
JFrame.setVisible(true);
}
@Override
public boolean accept(Object sender) {
return false;
}
}
TransactionViewerPopupMenu类,通过jd-gui反编译得到,反编译后有错误将.换成$符号即可
package com.xk72.charles.gui.transaction.popups;
import com.xk72.charles.gui.session.popups.TransactionPopupMenu;
import com.xk72.charles.gui.transaction.actions.Base64DecodeAction$Text;
import com.xk72.charles.gui.transaction.actions.Base64DecodeAction$TextComponent;
import com.xk72.charles.gui.transaction.actions.CopyToClipboardAction$Text;
import com.xk72.charles.gui.transaction.actions.CopyToClipboardAction$TextComponent;
import com.xk72.charles.gui.transaction.actions.CustomDecrypt;
import com.xk72.charles.model.Transaction;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.MouseEvent;
public class TransactionViewerPopupMenu extends TransactionPopupMenu {
// 定义 Transaction
private final Transaction transaction;
public TransactionViewerPopupMenu(Transaction paramTransaction) {
super(paramTransaction, null, null, null);
// 接收
this.transaction = paramTransaction;
}
@Override
protected void prepare(MouseEvent paramMouseEvent) {
Component component = (Component)paramMouseEvent.getSource();
if (component instanceof JTable) {
JTable jTable = (JTable)component;
Point point = paramMouseEvent.getPoint();
int i = jTable.rowAtPoint(point);
int j = jTable.columnAtPoint(point);
if (i >= 0 && j >= 0) {
Object object = jTable.getValueAt(i, j);
if (object != null) {
add((Action)new CopyToClipboardAction$Text(object.toString()));
if (object instanceof String)
add((Action)new Base64DecodeAction$Text((String)object, component));
addSeparator();
}
}
} else if (component instanceof JTextComponent) {
add((Action)new CopyToClipboardAction$TextComponent((JTextComponent)component));
add((Action)new Base64DecodeAction$TextComponent((JTextComponent)component));
// 新增一个按钮,执行按钮的时候会调用CustomDecrypt的actionPerformed方法
add((Action)new CustomDecrypt(this.transaction));
addSeparator();
}
prepare(false);
}
}
3.3,将上面的CustomDecrypt类和TransactionViewerPopupMenu类,编译成class
3.4,执行命令前进入到out\production\custom-decrypt路径
执行如下命令,意思是将这两个类的class加到charles.java包里面
jar -uvf H:\learn\custom-decrypt\libs\charles.jar com\xk72\charles\gui\transaction\actions\CustomDecrypt.class
jar -uvf H:\learn\custom-decrypt\libs\charles.jar com\xk72\charles\gui\transaction\popups\TransactionViewerPopupMenu.class
出现正在添加,表示执行成功
3.5,将项目中libs下的charles.jar替换到charles的安装路径下的lib目录,例如:D:\charles\location\lib
4,测试
替换后,打开charles软件,例如编写一个测试接口(根据需要自己编写),抓包如下
点击按钮后
结语:以上笔记已经非常详细,主要给自己做个记录,然后也能分享一下技术,整体来看需要花点时间,如果有问题,不负责任,仅供参考,毕竟能做程序员的都不笨,别跟个别人一样白嫖了文章,自己不会还要骂人,我真是擦了尼玛
参考了以下文章:charles 增加 自定义解密功能 · TesterHome