会话内容存档解密
0.说明
感谢:
https://github.com/hufeiyaya/QyChat 提供了参考代码。
https://blog.csdn.net/u011056339/article/details/105704995 提供了参考代码和公钥秘钥生成工具。
写该文章的原因是:
0.本人对公钥秘钥对加密不了解...记录一下!
1.工作原因,需要将企业微信的聊天记录进行留存。
2.在网上找到了代码,但是踩了坑,因此想写详细一些分享出来,避免其他人踩坑。(其实半天就可以搞定的,但是我做了一两天...)
1.前期准备
1.1.下载会话内容存档SDK
-在会话内容存档功能中的API文档下
image-20200717160656444.png
https://work.weixin.qq.com/wework_admin/frame#financial/corpEncryptData
windows:
https://wwcdn.weixin.qq.com/node/wework/images/sdk_win.7z
Linux:
https://wwcdn.weixin.qq.com/node/wework/images/sdk_20200401.zip
1.2.创建公钥与私钥(PKCS1 PKCS8都可以,解密方式不同)
http://tool.chacuo.net/cryptrsaprikey
1.2.1.生成公钥私钥对
image-20200717161429829.png
1.2.2.配置公钥
每次设置版本会+1
(踩坑的原因是我没有认真看API文档说明,因为我阅读能力差)
坑:当前公钥版本下接收存档的消息,必须要由当前版本公钥对应的私钥进行解密。当时我在测试的时候一直用第一条数据进行测试,但是我的公钥版本已经变了好几次了。因此测试的时候,拿最后面最新的数据,被公钥加密的消息,进行解密。
image-20200717162442146.png
--Q:如何查看消息公钥版本?(Demo跑起来再回来看)
//参考API文档参数说明,根据需要进行修改
Finance.GetChatData(sdk, 100, 1000, "", "", 10, slice);
System.out.println("getChatData :" + Finance.GetContentFromSlice(slice));
image-20200717162951705.png
image-20200717163150985.png
2.创建Java工程
image-20200717161542830.png
我是直接将SDK的工程导入了IDEA,结构如图
3.创建测试Demo
创建Bean
//简写,setter/getter需要自行生成哦
public class ChatDatas implements Serializable {
private static final long serialVersionUID = 1700586620843625231L;
private String errcode;
private String errmsg;
private List<Qychat> chatdata;
....
//简写,setter/getter需要自行生成哦
public class Qychat implements Serializable{
private static final long serialVersionUID = 1L;
private String seq;
private String msgid;
private String publickey_ver;
/**加密RSA秘钥*/
private String encrypt_random_key;
/**加密消息*/
private String encrypt_chat_msg;
//测试类
public class FinanceTestDemo {
/**
* 打开企业微信管理端-我的企业-最下面
* */
private static String corpid = "企业微信id";
/**
* 打开企业微信管理端-管理工具-会话内容存档-secret
* */
private static String secret = "会话内容存档-secret";
public static void main(String[] args){
long sdk = Finance.NewSdk();
System.out.println(Finance.Init(sdk, corpid,secret));
long slice = Finance.NewSlice();
//参考API文档参数说明,根据需要进行修改
Finance.GetChatData(sdk, 100, 1000, "", "", 10, slice);
System.out.println("getChatData :" + Finance.GetContentFromSlice(slice));
JSONObject chatResultJson = JSONObject.parseObject(Finance.GetContentFromSlice(slice));
ChatDatas cdata = JSON.toJavaObject(chatResultJson, ChatDatas.class);
List<Qychat> list = cdata.getChatdata();
for (Qychat qychat : list) {
String encrypt_chat_msg = qychat.getEncrypt_chat_msg();
String privateKey = null;
try {
//注意生成公钥时使用的是 PKCS8还是PKCS1
privateKey = RSAUtil_PKCS8.getPrivateKey(qychat.getEncrypt_random_key());
} catch (Exception e) {
// e.printStackTrace();
}
System.out.println(privateKey);
// 将获取到的数据进行解密操作
long msg = Finance.NewSlice();
Finance.DecryptData(sdk, privateKey, encrypt_chat_msg, msg);
String content = Finance.GetContentFromSlice(msg);
System.out.println("------------获得解密后的内容-----------"+content);
}
Finance.FreeSlice(slice);
Finance.DestroySdk(sdk);
}
}
4.创建工具类(两种:PKCS1/PKCS8)
/**
* 1.生成公钥私钥对: http://tool.chacuo.net/cryptrsaprikey
* -1.1选择RSA秘钥对,2048位+PKCS#1,生成的【公钥】放在企业微信的配置中用来加密(消息加密公钥-版本号:x [设置]),【私钥】放在下面用来解密
* -1.2公钥版本不同,解密的私钥也不同,在企业微信后台中,每次修改公钥版本都会改变
* -1.3消息存档时,已经被当前版本公钥加密。因此,会出现 不同消息使用不同公钥的情况。
*
* 2.参考博客:https://blog.csdn.net/u011056339/article/details/105704995
*
* 3.Java RSA 加密工具类 参考: https://blog.csdn.net/qy20115549/article/details/83105736
*/
4.1.RSAUtil_PKCS1
public class RSAUtil_PKCS1 {
private static final String privKeyPEM = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIICXAIBAAKBgQDV+keBFjpFITkYRRWQ8xcBXvWuRWsdI5pO17olqL2i7zTIeiD+\n" +
"Gu4m77TCrEmoh7j9/imwKDA4CD/fCtI/JWMSR4YFWFBzfsNZZYOpSye03a9r5Oiy\n" +
"wnUGha06mQiJfHdZ87bKixrWdRbs9VQISbaPCLHDjY+tO0qJ1dy/rK9LqwIDAQAB\n" +
"AoGAS4cCizVpzvHZoc/su1OamLDIfkFhfBEPi+nIejdz7FmLo4G8OIUP761ne9lt\n" +
"TG/Po9N9KoEc+AItbVB4ArLzIyBBnNLMBZN3pEbOAdosgIGWKQ5PtJYqMgHwf2rB\n" +
"Mh+q2+zqO0Q/KkQcF1+FqgxMP0gULVnsy8UqgTdKD3E60/UCQQD/ahv3hBk05GuW\n" +
"ev1DwTjbwgTw2cG1a48FobSFNkkDqQNYSHmLjAMaUbcPQclAOJAvziaoHKbNWm0o\n" +
"E9owe0xnAkEA1nfaTP8DByRQeAIE6LEgjpmBCxoR/gnblfOSTaYkMrLi6bytdIF9\n" +
"d/MBMUCakeyqvgweIcFsw3J3v7RFZwS8HQJAfZLQry9+KRgIoVJUhGRSLRFF1pho\n" +
"+WYpSg6Hr1rSKP+GingPcgFjYSQ9yT2B0ZY9pZNIRCzaAWps8mBYTK/CDwJBAKuE\n" +
"VnVVTFqd1CzlkH93iI4CfY0fYFxGDfKyRMMMp85T+dzsI9wU4v7WvJFjFTq2hyZO\n" +
"Epr0UcNM/C+4P/jPdJECQFec9fqAltWb6Vjqta2spesB/4W0ZqYt3GyWPMCYOLlr\n" +
"knhoOD7Sve0bTQNHRa5HKLufuV6zbMk2t8mbQKRcKwk=\n" +
"-----END RSA PRIVATE KEY-----";
// 用此方法先获取秘钥
public static String getPrivateKey(String encrypt_random_key) throws Exception {
String privKeyPEMnew = privKeyPEM.replaceAll("\\n", "").replace("-----BEGIN RSA PRIVATE KEY-----", "").replace("-----END RSA PRIVATE KEY-----", "");
byte[] bytes = java.util.Base64.getDecoder().decode(privKeyPEMnew);
DerInputStream derReader = new DerInputStream(bytes);
DerValue[] seq = derReader.getSequence(0);
BigInteger modulus = seq[1].getBigInteger();
BigInteger publicExp = seq[2].getBigInteger();
BigInteger privateExp = seq[3].getBigInteger();
BigInteger prime1 = seq[4].getBigInteger();
BigInteger prime2 = seq[5].getBigInteger();
BigInteger exp1 = seq[6].getBigInteger();
BigInteger exp2 = seq[7].getBigInteger();
BigInteger crtCoef = seq[8].getBigInteger();
RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// 64位解码加密后的字符串
byte[] inputByte = Base64.getMimeDecoder().decode(encrypt_random_key);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
String s = new String(cipher.doFinal(inputByte));
return s;
}
}
4.2.RSAUtil_PKCS8
public class RSAUtil_PKCS8 {
private static final String privKeyPEM = "-----BEGIN PRIVATE KEY-----\n" +
"MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDgAqLhj+56BOjr\n" +
"1Q9M/Te87Wr1u0zkUz8Xk+6buQgTpKsKoNNOb7R05K6GKnipNyppazOO6jRKDB1N\n" +
"vtOj7yX1oDZzSQTwlYiC3+4HqFKfVU9D9C+BLq15IW3VFE9hJubT8H93wpKYAgeC\n" +
"YvUZ6nl9yqdsk0worW7qHvHqr+BUQkoU6x2XNwqemvxhcgeptxJUceqQz0qivgc6\n" +
"qgdswRgeTWlBsItXDqdQxDhNl7P1mFGknzfspWMvTEzVh0f6K/ADT+EDnrD3CP81\n" +
"ExIeLkquVw3uBPBsqfkew8IqMTeRgD/riNjT7ZkGGI0h7KJtSvIeEePbExrIEOr0\n" +
"nFBlrFfRAgMBAAECggEBAKdj0OrUbtNnD9YKI6DYJupaTu7IwzBqtF6eAFME9PAY\n" +
"wGb0vnGCL0qaB3/iBMANpPeZT7GfeOtMGimaSvPZJHhi+80x5ysP0i5ZvriiIvtE\n" +
"+DJDKaxSgPZe8H+k6ZwjQaFluRp4nqpP+eSIpbZz68z/vhP4DZTn5FW2Qfeo7OyI\n" +
"Jnd4uVOBPd3cfjqkJqftWYnoM5znE/eINISp6LbZWO3YOWNJkPaUULHiynPYkhxx\n" +
"/ePZXoaSyMwJxU1/5fzWxyifoNGKfGm/rkBjNfkiujIwvCKRZXTmF9n34I3eS2VM\n" +
"fwLf6PXaoy7adP25BVDHyZi0WsFHzJaZKABKVcCGFwECgYEA8/BVJ6Yhayzbkzsq\n" +
"qQ1jeaOmbzvwXn4/bq6v1Jl8wMjjZxay7lPFihODsJBw5bIstxEl6+InLZKVxT1E\n" +
"fToULVbLG4l0+XJznpGmd94EG4l0A6PhFRQAkuwR3J0/AK47cJUMUYIdjjC/zyFr\n" +
"1wasWB2lOZWfH1zBiDPK4m99aXkCgYEA6xYOwMmdMgxikf2410KqMpdmsCTpCczR\n" +
"gN9KZthbstxMQfd0BlsvJd0Rtg+628lpFRk3q1lA97Q+f/IQC9wmYGEqaYJiWgHw\n" +
"GwdoYya2uR+vlvRGppl8GrvdhcDPCY6lnwSxSP+zmXO7Cl0vq0tzqT4OgrjcxdXg\n" +
"98VCeo4toxkCgYEAmFU2+EGYkPM8U58ZLuS7gBSgNMp7eqbgOeBA0UTgUQuiZpgY\n" +
"ORh7PZSeIj6xId+4aMH+qmVaDe2CNd/iy0jfnMicoZ+fOr8sUJOoHya37fJSToui\n" +
"XaVWDmn5ZYmU9HnZiJ6rSKM2jbsHrPO0Al2adpRcv68d5VnpSYL+aZUx/hECgYEA\n" +
"mL6uO0lEX/54FU97yDHCkDibOhvhZsKz5T4wA37UpfRJgBseQfsBOWLYXSj/Sksl\n" +
"gdXuu+C0O73bVhqbnnkeXkUD36Yd7UmRDp7Tjoja9JHH7xcsyJa1clFab8uFOjp7\n" +
"FkVgQ4QQ18XAY82EaZIOxopRt1IR8GE1WQfMWAEFWhECgYEA3XNF5z+Yc+NS/cbj\n" +
"JOue18iCHAvR2LPbyiabtgj9o3omhrJ//xayL0zUjpqd7URffBKw25uxjXYppxgB\n" +
"JhBJ5/bRmHUWadk62d+4Jei+VKz5ZOv2fBAFnE5rq0t6wuPIEvQ7+pYw6iAiASmM\n" +
"R6xBKhxiWW3le10SzqFtOCV/ZkY=\n" +
"-----END PRIVATE KEY-----\n";
// 用此方法先获取秘钥
public static String getPrivateKey(String encrypt_random_key) throws Exception {
String privKeyPEMnew = privKeyPEM.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "");
byte[] decoded = Base64.getDecoder().decode(privKeyPEMnew);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(decoded));
// 64位解码加密后的字符串
byte[] inputByte = Base64.getDecoder().decode(encrypt_random_key);
// RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
}
5.测试结果
image-20200717163457038.png