作者:Hogan
链接:http://www.588zj.com/article/qywxDev
声明:请尊重原作者的劳动,如需转载请注明出处
前阵子,接手的项目需要实现企业微信会话内容存档(下称企微存档),开发期间,踩得坑一个又一个,悲惨至极~为了避免日后遗忘,来这里记录一下
Java调用企微存档SDK踩到的坑
首先我是从企业微信官方文档那里下载了企微存档的SDK,长这样子:
看起来没毛病,调用也走得通.然后我就想直接把Finance.java和所有的.dll文件拿到我接手的项目里,并且放到想放的包或者目录中,然后duang,duang,duang,问题就来了.
问题1:.dll文件放到放的目录中,比如resources下新建的文件夹lib中,程序会提示找不到文件.怎么解决呢,搜索引擎大法走起,
关于.dll文件存放目录,网上说的大概有几种:
①把dll文件丢到java/jdk/bin目录下------------想都不想,直接过掉
②把dll文件丢到C:\Windows\System32下-----------------就更离谱了
③放到项目中自己新建的目录,比如我初衷想要的 resources.lib目录,然后修改 IDEA (我用的是IDEA)运行配置中的 VM option 为:-Djava.library.path=D:\***\lib ----------------好家伙,可以找到,但是加载到的.dll文件却找不到它本身依赖的.dll文件- -
解决:最后,好吧,我屈服了,还是直接把.dll文件放在src同级目录下了.
问题2:我把Finance.java放到项目中的随便一个包下面,调用的时候却报错java.lang.UnsatisfiedLinkError.
这里报错的原因是包的路径和.dll头文件的路径不一致
解决:把Finance.java放到和人家给的SDK同样的包com.tencent.wework下,问题得到解决
企微存档解码踩到的坑
我对解密实在是陌生,再加上企微存档的文档写得实在是不咋样,踩了挺多坑的.查了很多,最后找到了下面这位老哥的博客,完美解决解码的问题
企业微信那边的配置就不多说了,可以参照上面微博所说的,到http://web.chacuo.net/netrsakeypair获取密钥对,把公钥设置到企业微信会话存档设置页面"消息加密公钥"处,把私钥存起来......
解密的相关代码如下:
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.Base64;
public class RSAUtil {
/**
* RSA pkcs1 2048bit 解密工具,
* 获取私钥PrivateKey
* @param privKeyPEM 2048bit pkcs1格式,base64编码后的RSA字符串
* @return PrivateKey,用于解密 decryptRSA
* @throws IOException 异常
* @throws NoSuchAlgorithmException 异常
* @throws InvalidKeySpecException 异常
*/
public static PrivateKey getPrivateKey(String privKeyPEM) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
String privKeyPEMnew = privKeyPEM.replaceAll("\\r\\n", "")//看看保存到数据库的这个字符是不是有\r,如果没有,那就只需要替换掉"\\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);
return privateKey;
}
/**
* RSA pkcs1 2048bit 解密工具,
* @param str 被解密的字符串
* @param privateKey 私钥对象 从 getPrivateKey 获取
* @return 解密后数据
* @throws NoSuchPaddingException 异常
* @throws NoSuchAlgorithmException 异常
* @throws InvalidKeyException 异常
* @throws BadPaddingException 异常
* @throws IllegalBlockSizeException 异常
*/
public static String decryptRSA(String str, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] randomkeybyte = Base64.getDecoder().decode(str);
byte[] finalrandomkeybyte = cipher.doFinal(randomkeybyte);
return new String(finalrandomkeybyte);
}
}
说说怎么用吧.首先我们拿到的是这种格式的数据
{"errcode":0,"chatdata":[{"publickey_ver":2,"encrypt_chat_msg":"1LrkVikw/PxL+FYraimNB+7yEZ9mkOB2Yae7X6pow+wToQb/rYHnHEN3sDIUUuLX7wENOjuRo2yk7VIDq4Bb7j8q3Vfwb5BqGa/hkUYJZj96OJiPLgMJN8+adAFWLVXdVX2MZTVV8qrpDlkYqvHY6ImVj6jDueRVGCd7q0nOetkujEsZCH82ZsggQ2JehJrJ9ROOf/tJBRkIGT2nHkhUlpqK3BvIQ5ZLMNQM51sZXpFt1h4nYxsY/rD+Vwa+HE0ahXGh3a6Eyb9SHOUlzeEadwqPBZD5sORESOXVXG3Gr1imhpxrDE51IfRSFY5iPqOhwFEtMkhZD5xNl/pjE7gJKZLVMZp82GpTn9TBZM=Ia4NqYo4WhZ+C9u","msgid":"13000540326617473990_1589764124","encrypt_random_key":"bGCT7X4LmQTEGpHkXDWRwDwbibeGDvk+INbrW+/T5N23VZWA8iwrlmIcWqM1SlsGDfkELbAg0dpurVsTGuS1p1LLY0kB+ZZvF8qjbJk56BETXiBevgrPwCfukcv0VDkAAjGX+BysQBvScLwN8k/fpfs9ydYDLPaDzQXHVLJRxkM=","seq":4}],"errmsg":"ok"}
如下图,一定要用对应版本的私钥解密,不然会解密失败
我是这样封装的方法,亲测有效,需要的可以参考或者直接拿去使用(tips:RSAUtil在上面已经给出,包括import的包,Finance是人家企微存档的SDK,JSON用的com.alibaba.fastjson)
/**
* @param sdk 初始化时候获取到的值
* @param ncrypt_random_key 企业微信返回的随机密钥
* @param encrypt_chat_msg 企业微信返回的单条记录的密文消息
* @param privateKey 企业微信管理后台设置的私钥,!!!版本记得对应上!!!
* @return JSONObject 返回不同格式的聊天数据,格式有二十来种
* 详情请看官网 https://open.work.weixin.qq.com/api/doc/90000/90135/91774#%E6%B6%88%E6%81%AF%E6%A0%BC%E5%BC%8F
*/
public static JSONObject decryptChatRecord(Long sdk, String ncrypt_random_key, String encrypt_chat_msg, String privateKey){
Long msg = null;
try {
//获取私钥
PrivateKey privateKeyObj = RSAUtil.getPrivateKey(privateKey);
String str = RSAUtil.decryptRSA(ncrypt_random_key, privateKeyObj);
//初始化参数slice
msg = Finance.NewSlice();
//解密
Finance.DecryptData(sdk, str, encrypt_chat_msg, msg);
String jsonDataStr = Finance.GetContentFromSlice(msg);
logger.info("解析数据:------------" + jsonDataStr);
JSONObject realJsonData = (JSONObject) JSON.parseObject(jsonDataStr);
return realJsonData;
} catch (Exception e) {
logger.error("解析密文失败");
return null;
} finally {
if(msg != null){
//释放参数slice
Finance.FreeSlice(msg);
}
}
}