企业微信会话存档功能接入

  • 简介
  • 官方文档
  • 上代码
  • 参考链接

简介

最近有个需求需要接入企业微信,将员工和客户的聊天记录存档,于是翻阅企业微信开放平台接口文档,写的那叫一个“好”(lan)。
网上文档也比较少,仔细阅读,翻阅一些文章后还是搞定了,于是分享一下。

官方文档

encrypt_random_key内容解密说明:
encrypt_random_key是使用企业在管理端填写的公钥(使用模值为2048bit的秘钥),采用RSA加密算法进行加密处理后base64 encode的内容,加密内容为企业微信产生。RSA使用PKCS1。
企业通过GetChatData获取到会话数据后:
a) 需首先对每条消息的encrypt_random_key内容进行base64 decode,得到字符串str1.
b) 使用publickey_ver指定版本的私钥,使用RSA PKCS1算法对str1进行解密,得到解密内容str2.
c) 得到str2与对应消息的encrypt_chat_msg,调用下方描述的DecryptData接口,即可获得消息明文。

看到这一串有点懵逼。翻译一下:
需要先接受到消息,接受到消息之后有一个encrypt_random_key字段,需要先对这个字段进行解密,解密需要用到秘钥进行,这个秘钥对应着公司的企业微信管理后台设置的公钥使用模值为2048bit的秘钥,使用rsa PKCS1算法进行解密。

具体怎么做呢

首先使用http://tool.chacuo.net/cryptrsapubkey 生成生成2048位 PKCS1秘钥对

企业微信 会话保存接收 java 企业微信会话存储_字符串


将公钥复制到企业管理后台。

注意:java私钥需要把pkcs1转成pkcs8!!(我也不知道为什么,参考的github项目https://github.com/yang2wuhen/work_wx

http://www.metools.info/code/c84.html 使用这个可以直接转换。转换后的私钥为解密的私钥。

上代码

public void synchronizationMessage() {
        int init = FinanceUtils.init(corpId, messageSecret);
        // 不为0 则为失败
        if (CommonConstant.INTEGER_ZERO != init){
            log.error("初始化微信sdk失败");
        }else {
            long slice = Finance.NewSlice();
            int i = Finance.GetChatData(FinanceUtils.sdk, 311, 1000, "", "", 3, slice);
            String getContentFromSlice = Finance.GetContentFromSlice(slice);
            log.info(getContentFromSlice);
            Finance.FreeSlice(slice);
            GetWeChatMessageResponseVo getWeChatMessageResponseVo = JSONObject.parseObject(getContentFromSlice, GetWeChatMessageResponseVo.class);
            if (0 != getWeChatMessageResponseVo.getErrCode()){
                log.error("获取消息失败");
            }else {
                slice = Finance.NewSlice();
                List<WeChatMessageChatDataResponseVo> chatDataList = getWeChatMessageResponseVo.getChatData();
                for (WeChatMessageChatDataResponseVo weChatMessageChatDataResponseVo : chatDataList) {
                    // encrypt_random_key内容解密说明:
                    //encrypt_random_key是使用企业在管理端填写的公钥(使用模值为2048bit的秘钥),采用RSA加密算法进行加密处理后base64 encode的内容,加密内容为企业微信产生。RSA使用PKCS1。
                    //企业通过GetChatData获取到会话数据后:
                    //a) 需首先对每条消息的encrypt_random_key内容进行base64 decode,得到字符串str1.
                    //b) 使用publickey_ver指定版本的私钥,使用RSA PKCS1算法对str1进行解密,得到解密内容str2.
                    //c) 得到str2与对应消息的encrypt_chat_msg,调用下方描述的DecryptData接口,即可获得消息明文。
                    String encryptRandomKey = weChatMessageChatDataResponseVo.getEncryptRandomKey();

                    String decryptByPrivateKey = RSAUtils.decryptByPriKey(encryptRandomKey, PRIKEY_PKCS8);
                    int decryptData = Finance.DecryptData(FinanceUtils.sdk, decryptByPrivateKey, weChatMessageChatDataResponseVo.getEncryptChatMsg(),
                            slice);
                    if (CommonConstant.INTEGER_ZERO != decryptData){
                        log.error("解密消息失败, encrypt_random_key = {}, PRIKEY_PKCS8 = {}, decryptByPrivateKey = {}, encrypt_chat_msg = {}",
                                encryptRandomKey, PRIKEY_PKCS8, decryptByPrivateKey, weChatMessageChatDataResponseVo.getEncryptChatMsg());
                    }else {
                        String message = Finance.GetContentFromSlice(slice);
                        log.info("获取到解密消息{}", message);
                        JSONObject chatVo = JSONObject.parseObject(message);

                    }
                }
            }
            Finance.FreeSlice(slice);
        }
    }

这个其实没啥作用

public class FinanceUtils {

    public static long sdk = 0;

    public static int init(String corpId, String messageSecret) {
        if (sdk == 0) {
            sdk = Finance.NewSdk();
            return Finance.Init(sdk, corpId, messageSecret);
        }else {
            return 0;
        }
    }
}

解密工具类

package com.iresearch.community.utils;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * RSAUtils
 * @author 小浩
 * @date 2020/11/26
 * @since 1.0.0
 */
@Slf4j
public class RSAUtils {

    /**
     * 数字签名,密钥算法
     */
    private static final String RSA_KEY_ALGORITHM = "RSA";

    /**
     * 数字签名签名/验证算法
     */
    private static final String SIGNATURE_ALGORITHM = "MD5withRSA";

    /**
     * RSA密钥长度,RSA算法的默认密钥长度是1024密钥长度必须是64的倍数,在512到65536位之间
     */
    private static final int KEY_SIZE = 1024;

    /**
     * 生成密钥对
     */
    private static Map<String, String> initKey() throws Exception {
        KeyPairGenerator keygen = KeyPairGenerator.getInstance(RSA_KEY_ALGORITHM);
        SecureRandom secrand = new SecureRandom();
        /**
         * 初始化随机产生器
         */
        secrand.setSeed("initSeed".getBytes());
        /**
         * 初始化密钥生成器
         */
        keygen.initialize(KEY_SIZE, secrand);
        KeyPair keys = keygen.genKeyPair();

        byte[] pub_key = keys.getPublic().getEncoded();
        String publicKeyString = org.apache.commons.codec.binary.Base64.encodeBase64String(pub_key);

        byte[] pri_key = keys.getPrivate().getEncoded();
        String privateKeyString = org.apache.commons.codec.binary.Base64.encodeBase64String(pri_key);

        Map<String, String> keyPairMap = new HashMap<>();
        keyPairMap.put("publicKeyString", publicKeyString);
        keyPairMap.put("privateKeyString", privateKeyString);

        return keyPairMap;
    }

    /**
     * 密钥转成字符串
     *
     * @param key
     * @return
     */
    public static String encodeBase64String(byte[] key) {
        return org.apache.commons.codec.binary.Base64.encodeBase64String(key);
    }

    /**
     * 密钥转成byte[]
     *
     * @param key
     * @return
     */
    public static byte[] decodeBase64(String key) {
        return Base64.decodeBase64(key);
    }

    /**
     * 公钥加密
     *
     * @param data      加密前的字符串
     * @param publicKey 公钥
     * @return 加密后的字符串
     * @throws Exception
     */
    public static String encryptByPubKey(String data, String publicKey) throws Exception {
        byte[] pubKey = RSAUtils.decodeBase64(publicKey);
        byte[] enSign = encryptByPubKey(data.getBytes(), pubKey);
        return org.apache.commons.codec.binary.Base64.encodeBase64String(enSign);
    }

    /**
     * 公钥加密
     *
     * @param data   待加密数据
     * @param pubKey 公钥
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPubKey(byte[] data, byte[] pubKey) throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 私钥加密
     *
     * @param data       加密前的字符串
     * @param privateKey 私钥
     * @return 加密后的字符串
     * @throws Exception
     */
    public static String encryptByPriKey(String data, String privateKey) throws Exception {
        byte[] priKey = RSAUtils.decodeBase64(privateKey);
        byte[] enSign = encryptByPriKey(data.getBytes(), priKey);
        return org.apache.commons.codec.binary.Base64.encodeBase64String(enSign);
    }

    /**
     * 私钥加密
     *
     * @param data   待加密的数据
     * @param priKey 私钥
     * @return 加密后的数据
     * @throws Exception
     */
    public static byte[] encryptByPriKey(byte[] data, byte[] priKey) throws Exception {
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     *
     * @param data   待解密的数据
     * @param pubKey 公钥
     * @return 解密后的数据
     * @throws Exception
     */
    public static byte[] decryptByPubKey(byte[] data, byte[] pubKey) throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     *
     * @param data      解密前的字符串
     * @param publicKey 公钥
     * @return 解密后的字符串
     * @throws Exception
     */
    public static String decryptByPubKey(String data, String publicKey) throws Exception {
        byte[] pubKey = RSAUtils.decodeBase64(publicKey);
        byte[] design = decryptByPubKey(org.apache.commons.codec.binary.Base64.decodeBase64(data), pubKey);
        return new String(design);
    }

    /**
     * 私钥解密
     *
     * @param data   待解密的数据
     * @param priKey 私钥
     * @return
     * @throws Exception
     */
    @SneakyThrows
    public static byte[] decryptByPriKey(byte[] data, byte[] priKey) {
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return getMaxResultDecrypt(data, cipher);
    }

    /**
     * 私钥解密
     *
     * @param data       解密前的字符串
     * @param privateKey 私钥
     * @return 解密后的字符串
     * @throws Exception
     */
    @SneakyThrows
    public static String decryptByPriKey(String data, String privateKey) {
        byte[] priKey = RSAUtils.decodeBase64(privateKey);
        byte[] design = decryptByPriKey(Base64.decodeBase64(data.getBytes("UTF-8")), priKey);
        return new String(design);
    }

    /**
     * RSA签名
     *
     * @param data   待签名数据
     * @param priKey 私钥
     * @return 签名
     * @throws Exception
     */
    public static String sign(byte[] data, byte[] priKey) throws Exception {
        // 取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        // 生成私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        // 实例化Signature
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 初始化Signature
        signature.initSign(privateKey);
        // 更新
        signature.update(data);
        return Base64.encodeBase64String(signature.sign());
    }

    /**
     * RSA校验数字签名
     *
     * @param data   待校验数据
     * @param sign   数字签名
     * @param pubKey 公钥
     * @return boolean 校验成功返回true,失败返回false
     */
    public boolean verify(byte[] data, byte[] sign, byte[] pubKey) throws Exception {
        // 实例化密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        // 初始化公钥
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
        // 产生公钥
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        // 实例化Signature
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 初始化Signature
        signature.initVerify(publicKey);
        // 更新
        signature.update(data);
        // 验证
        return signature.verify(sign);
    }

    private static byte[] getMaxResultDecrypt(byte[] inputArray, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
        int inputLength = inputArray.length;
        // 最大解密字节数,超出最大字节数需要分组加密
        int MAX_ENCRYPT_BLOCK = 128;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }
}

企业微信 会话保存接收 java 企业微信会话存储_ci_02


还要注意的地方是这个sdk的文件要和demo的保持一致。

参考链接


https://github.com/yang2wuhen/work_wx