三方平台AES加密解密
三方平台包括:微信、抖音、百度、小红书
调用示例
public static void main(String[] args) {
String encryptedData = "";
String iv = "";
String sessionKey = "";
String decrypt = WxBizMsgCrypt.decrypt(encryptedData, iv, sessionKey);
System.out.println(decrypt);
}
基础代码
maven依赖
<!--Base64Utils依赖,可自行替换成其他类库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.25</version>
</dependency>
<!--XML解析包-->
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.1.3</version>
</dependency>
抽象工具类
package com.honyee.app.utils.crypt;
import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
* 解密工具公共方法
* @author honyee
*/
public class BizMsgCryptUtil {
private static final Charset CHARSET = StandardCharsets.UTF_8;
private static final Logger log = LoggerFactory.getLogger(BizMsgCryptUtil.class);
private BizMsgCryptUtil() {
}
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 对密文进行解密,主要用于服务端推送消息解密
*
* @param appid 需要匹配的小程序appid
* @param encryptData 密文
* @param aesKey 密钥
* @param pos 移位
* @return 明文
*/
public static String decrypt(String appid, String encryptData, byte[] aesKey, int pos) {
byte[] original;
try {
// 设置解密模式为AES的CBC模式
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
// 使用BASE64对密文进行解码
byte[] encrypted = Base64.getDecoder().decode(encryptData);
// 解密
original = cipher.doFinal(encrypted);
} catch (Exception e) {
throw new RuntimeException("解密失败:" + e.getMessage());
}
String content;
String fromAppid;
try {
// ==== 分离随机字符串,网络字节序和AppId
// 去除补位字符
byte[] bytes = PKCS7.decode(original);
// 获取内容长度,byte数组的第pos 至 pos+4 个元素代表了消息体的真实字符个数,也就是长度
int contentLength = recoverNetworkBytesOrder(Arrays.copyOfRange(bytes, pos, pos + 4));
// 内容
content = new String(Arrays.copyOfRange(bytes, pos + 4, pos + 4 + contentLength), CHARSET);
// 携带的appid
fromAppid = new String(Arrays.copyOfRange(bytes, pos + 4 + contentLength, bytes.length), CHARSET);
} catch (Exception e) {
throw new RuntimeException("解密失败:" + e.getMessage());
}
// appid不相同的情况
if (!fromAppid.equals(appid)) {
throw new RuntimeException("解密失败:appid不相同");
}
return content;
}
// 生成4个字节的网络字节序
public static byte[] getNetworkBytesOrder(int sourceNumber) {
ByteBuffer b = ByteBuffer.wrap(new byte[4]);
b.asIntBuffer().put(sourceNumber);
return b.array();
}
// 生成4个字节的网络字节序 - 只是换了种实现方式
public static byte[] getNetworkBytesOrder2(int sourceNumber) {
byte[] orderBytes = new byte[4];
orderBytes[3] = (byte) (sourceNumber & 0xFF);
orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
return orderBytes;
}
// 还原4个字节的网络字节序
public static int recoverNetworkBytesOrder(byte[] orderBytes) {
ByteBuffer buf = ByteBuffer.wrap(orderBytes);
buf.order(ByteOrder.BIG_ENDIAN);
return buf.getInt();
}
// 还原4个字节的网络字节序 - 只是换了种实现方式
public static int recoverNetworkBytesOrder2(byte[] orderBytes) {
int sourceNumber = 0;
for (int i = 0; i < 4; i++) {
sourceNumber <<= 8;
sourceNumber |= orderBytes[i] & 0xff;
}
return sourceNumber;
}
// 随机生成16位字符串
public static String getRandomStr() {
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = ThreadLocalRandom.current();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 16; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
/**
* 对明文进行加密.
*
* @param text 需要加密的明文
* @return 加密后base64编码的字符串
*/
public static String encrypt(String appid, byte[] aesKey, String randomStr, String text) {
ByteGroup byteCollector = new ByteGroup();
byte[] randomStrBytes = randomStr.getBytes(CHARSET);
byte[] textBytes = text.getBytes(CHARSET);
byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
byte[] appidBytes = appid.getBytes(CHARSET);
// randomStr + networkBytesOrder + text + appid
byteCollector.addBytes(randomStrBytes);
byteCollector.addBytes(networkBytesOrder);
byteCollector.addBytes(textBytes);
byteCollector.addBytes(appidBytes);
// ... + pad: 使用自定义的填充方式对明文进行补位填充
byte[] padBytes = PKCS7.encode(byteCollector.size());
byteCollector.addBytes(padBytes);
// 获得最终的字节流, 未加密
byte[] unencrypted = byteCollector.toBytes();
try {
// 设置加密模式为AES的CBC模式
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
// 加密
byte[] encrypted = cipher.doFinal(unencrypted);
// 使用BASE64对加密后的字符串进行编码
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("加密失败:" + e.getMessage());
}
}
/**
* 对密文进行解密,主要用于移动端数据解密
*
* @param encryptData 密文
* @param iv 向量
* @param sessionKey 密钥
* @return 解密后的bytes,可能需要分离随机字符串后再转String
*/
public static byte[] decrypt(String encryptData, String iv, String sessionKey) {
byte[] aesKey = Base64Utils.decodeFromString(sessionKey);
byte[] aesIV = Base64Utils.decodeFromString(iv);
byte[] aesEncryptedData = Base64Utils.decodeFromString(encryptData);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(aesIV));
cipher.init(Cipher.DECRYPT_MODE, keySpec, params);
return cipher.doFinal(aesEncryptedData);
} catch (Exception e) {
log.error(e.getMessage());
}
return null;
}
/**
* 生成签名
*
* @param messageValidToken 消息验证token
* @param timestamp 时间戳,对应URL参数的timestamp
* @param nonce 随机串,对应URL参数的nonce
* @param encryptData 加密的数据或者随机串
* @return 签名
*/
public static String getSignature(String messageValidToken, String timestamp, String nonce, String encryptData) {
String[] array = {messageValidToken, timestamp, nonce, encryptData};
// 字符串排序
Arrays.sort(array);
return DigestUtils.sha1Hex(String.join("", array));
}
/**
* 验证
*
* @param messageValidToken 消息验证token
* @param msgSignature 签名串,对应URL参数的msg_signature
* @param timestamp 时间戳,对应URL参数的timestamp
* @param nonce 随机串,对应URL参数的nonce
* @param encryptData 加密的数据或者随机串
*/
public static void verify(String messageValidToken, String msgSignature, String timestamp, String nonce, String encryptData) {
if (!getSignature(messageValidToken, timestamp, nonce, encryptData).equals(msgSignature)) {
throw new RuntimeException("验证失败");
}
}
/**
* AES 解密
*
* @param messageEncodeDecodeKey 消息加密密钥
* @param encryptData 解密的数据
* @return 明文
*/
public static String decrypt(String messageEncodeDecodeKey, String encryptData) {
byte[] key = messageEncodeDecodeKey.getBytes(StandardCharsets.UTF_8);
key = Arrays.copyOfRange(key, 0, 16);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
IvParameterSpec iv = new IvParameterSpec(key);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
// 使用BASE64对密文进行解码
byte[] encrypted = Base64.getDecoder().decode(encryptData);
// 解密
byte[] original = cipher.doFinal(encrypted);
// 去除补位字符
byte[] bytes = PKCS7.decode(original);
// 内容
return new String(bytes, CHARSET);
} catch (Exception e) {
log.error(e.getMessage());
}
return null;
}
}
ByteGroup
package com.honyee.app.utils.crypt;
import java.util.ArrayList;
public class ByteGroup {
ArrayList<Byte> byteContainer = new ArrayList<>();
public byte[] toBytes() {
byte[] bytes = new byte[byteContainer.size()];
for (int i = 0; i < byteContainer.size(); i++) {
bytes[i] = byteContainer.get(i);
}
return bytes;
}
public ByteGroup addBytes(byte[] bytes) {
for (byte b : bytes) {
byteContainer.add(b);
}
return this;
}
public int size() {
return byteContainer.size();
}
}
PKCS7
package com.honyee.app.utils.crypt;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* 提供基于PKCS7算法的加解密接口.
*/
public class PKCS7 {
private PKCS7() {}
private static final Charset CHARSET = StandardCharsets.UTF_8;
private static final int BLOCK_SIZE = 32;
/**
* 获得对明文进行补位填充的字节.
*
* @param count 需要进行填充补位操作的明文字节个数
* @return 补齐用的字节数组
*/
static byte[] encode(int count) {
// 计算需要填充的位数
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
if (amountToPad == 0) {
amountToPad = BLOCK_SIZE;
}
// 获得补位所用的字符
char padChr = chr(amountToPad);
String tmp = new String();
for (int index = 0; index < amountToPad; index++) {
tmp += padChr;
}
return tmp.getBytes(CHARSET);
}
/**
* 删除解密后明文的补位字符
*
* @param decrypted 解密后的明文
* @return 删除补位字符后的明文
*/
static byte[] decode(byte[] decrypted) {
int padding = decrypted[decrypted.length - 1];
if (padding < 1 || padding > 32) {
padding = 0;
}
return Arrays.copyOfRange(decrypted, 0, decrypted.length - padding);
}
/**
* 将数字转化成ASCII码对应的字符,用于对明文进行补码
*
* @param a 需要转化的数字
* @return 转化得到的字符
*/
static char chr(int a) {
byte target = (byte) (a & 0xFF);
return (char) target;
}
}
XmlUtil
package com.honyee.app.utils.crypt;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* xml 解析和生成
*/
public class XmlUtil {
private static final Logger log = LoggerFactory.getLogger(XmlUtil.class);
private XmlUtil() {
}
/**
* 解析xml 字节点
*/
public static Map<String, String> doXMLChildParse(Object strxml) throws IOException, JDOMException {
return doXMLParse(String.format("<xml>%s</xml>", strxml));
}
public static Map<String, String> doXMLParseChild(Map<String, String> xmlMap, String childKey) throws IOException, JDOMException {
return doXMLParse("<xml>" + xmlMap.getOrDefault(childKey, "") + "</xml>");
}
public static Map<String, String> doXMLParseChild(String strxml) throws IOException, JDOMException {
return doXMLParse("<xml>" + strxml + "</xml>");
}
/**
* 解析xml
*/
public static Map<String, String> doXMLParse(String strxml) throws IOException, JDOMException {
if (strxml == null) {
return Collections.emptyMap();
}
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if ("".equals(strxml)) {
return Collections.emptyMap();
}
Map<String, String> map = new HashMap<>();
try (InputStream in = new ByteArrayInputStream(strxml.getBytes(StandardCharsets.UTF_8))) {
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
for (Object o : root.getChildren()) {
Element e = (Element) o;
String k = e.getName();
List<?> children = e.getChildren();
if (children.isEmpty()) {
map.put(k, e.getTextNormalize());
} else {
map.put(k, getChildrenText(children));
}
}
}
return map;
}
/**
* 获取子结点的xml
*/
public static String getChildrenText(List<?> children) {
StringBuilder sb = new StringBuilder();
if (children != null && !children.isEmpty()) {
for (Object child : children) {
Element e = (Element) child;
String name = e.getName();
String value = e.getTextNormalize();
List<?> list = e.getChildren();
sb.append(String.format("<%s>", name));
if (!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append(String.format("</%s>", name));
}
}
return sb.toString();
}
@SuppressWarnings("unused")
public static String getRequestXml(SortedMap<String, String> parameters) {
StringBuilder sb = new StringBuilder();
sb.append("<xml>");
Set<Map.Entry<String, String>> es = parameters.entrySet();
for (Map.Entry<String, String> e : es) {
String k = e.getKey();
String v = e.getValue();
if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
sb.append(String.format("<%s><![CDATA[%s]]></%s>", k, v, k));
} else {
sb.append(String.format("<%s>%s</%s>", k, v, k));
}
}
sb.append("</xml>");
return sb.toString();
}
@SuppressWarnings("unchecked")
public static Object[] parseXmlToList(String xml) {
List<Map<String, String>> argMapList = new ArrayList<>();
Map<String, String> retMap = new HashMap<>();
try (StringReader read = new StringReader(xml)) {
// 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
InputSource source = new InputSource(read);
// 创建一个新的SAXBuilder
SAXBuilder sb = new SAXBuilder();
// 通过输入源构造一个Document
Document doc = sb.build(source);
Element root = doc.getRootElement(); // 指向根节点
List<Element> es = root.getChildren();
if (es != null && !es.isEmpty()) {
for (Element element : es) {
retMap.put(element.getName(), element.getText());
}
}
argMapList.add(retMap);
} catch (Exception e) {
log.error(e.getMessage());
}
return new Object[]{argMapList};
}
/**
* 提取出xml数据包中的加密消息
*
* @param xmltext 待提取的xml字符串
* @return 提取出的加密消息字符串
*/
public static Object[] extract(String xmltext) {
Object[] result = new Object[3];
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(xmltext);
InputSource is = new InputSource(sr);
org.w3c.dom.Document document = db.parse(is);
org.w3c.dom.Element root = document.getDocumentElement();
NodeList nodelist1 = root.getElementsByTagName("Encrypt");
NodeList nodelist2 = root.getElementsByTagName("ToUserName");
result[0] = 0;
result[1] = nodelist1.item(0).getTextContent();
if (nodelist2 != null && nodelist2.item(0) != null) {
result[2] = nodelist2.item(0).getTextContent();
}
return result;
} catch (Exception e) {
log.error(e.getMessage());
throw new RuntimeException("xml解析失败");
}
}
/**
* 生成xml消息
*
* @param encrypt 加密后的消息密文
* @param signature 安全签名
* @param timestamp 时间戳
* @param nonce 随机字符串
* @return 生成的xml字符串
*/
public static String generate(String encrypt, String signature, String timestamp, String nonce) {
String format =
"<xml>\n" +
"<Encrypt><![CDATA[%1$s]]></Encrypt>\n" +
"<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" +
"<TimeStamp>%3$s</TimeStamp>\n" +
"<Nonce><![CDATA[%4$s]]></Nonce>\n" +
"</xml>";
return String.format(format, encrypt, signature, timestamp, nonce);
}
}
加密结果
package com.honyee.app.utils.crypt;
/**
* 加密结果
*
* @author honyee
*/
public class BizMsgEncryptResult {
private String timestamp;
private String nonce;
private String signature;
private String randomStr;
private String encryptData;
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getNonce() {
return nonce;
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getRandomStr() {
return randomStr;
}
public void setRandomStr(String randomStr) {
this.randomStr = randomStr;
}
public String getEncryptData() {
return encryptData;
}
public void setEncryptData(String encryptData) {
this.encryptData = encryptData;
}
public BizMsgEncryptResult timestamp(String timestamp) {
this.timestamp = timestamp;
return this;
}
public BizMsgEncryptResult nonce(String nonce) {
this.nonce = nonce;
return this;
}
public BizMsgEncryptResult signature(String signature) {
this.signature = signature;
return this;
}
public BizMsgEncryptResult randomStr(String randomStr) {
this.randomStr = randomStr;
return this;
}
public BizMsgEncryptResult encryptData(String encryptData) {
this.encryptData = encryptData;
return this;
}
}
对应小程序解密工具类
微信
package com.honyee.app.utils.crypt;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* 微信解密工具
*
* @author honyee
*/
public class WxBizMsgCrypt {
// 解密时使用,这个值一般从各个平台提供的解密demo中获取获取
private static final int RANDOM_BYTES_POS = 16;
final byte[] aesKey;
final String messageValidToken;
final String appid;
/**
* 构造函数
*
* @param messageValidToken 公众平台上,开发者设置的 message_valid_token
* @param encodingAesKey 公众平台上,开发者设置的 message_encode_decode_key
* @param appid 公众平台appid
*/
public WxBizMsgCrypt(String messageValidToken, String encodingAesKey, String appid) {
if (encodingAesKey.length() != 43) {
throw new RuntimeException("初始化失败:key长度不是43位");
}
this.messageValidToken = messageValidToken;
this.appid = appid;
aesKey = Base64.getDecoder().decode(encodingAesKey + "=");
}
/**
* 对密文进行解密 用于移动端数据解密
*
* @param encryptedData 密文
* @param iv 向量
* @param sessionKey 密钥
* @return 明文
*/
public static String decrypt(String encryptedData, String iv, String sessionKey) {
byte[] original = BizMsgCryptUtil.decrypt(encryptedData, iv, sessionKey);
if (null != original && original.length > 0) {
return new String(original, StandardCharsets.UTF_8);
}
return null;
}
/**
* 将公众平台回复用户的消息加密打包. 用于服务端推送消息加密
*
* @param text 公众平台待回复用户的消息,xml格式的字符串
* @param timestamp 时间戳,可以自己生成,也可以用URL参数的timestamp
* @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
*/
public String encryptMsg(String text, String timestamp, String nonce) {
String randomStr = BizMsgCryptUtil.getRandomStr();
// 加密
String encryptData = BizMsgCryptUtil.encrypt(appid, aesKey, randomStr, text);
// 生成安全签名
if ("".equals(timestamp)) {
timestamp = Long.toString(System.currentTimeMillis());
}
String signature = BizMsgCryptUtil.getSignature(messageValidToken, timestamp, nonce, encryptData);
// 生成发送的xml
return XmlUtil.generate(encryptData, signature, timestamp, nonce);
}
/**
* 检验消息的真实性,并且获取解密后的明文. 用于服务端推送消息解密
*
* @param msgSignature 签名串,对应URL参数的msg_signature
* @param timestamp 时间戳,对应URL参数的timestamp
* @param nonce 随机串,对应URL参数的nonce
* @param postData 密文,对应POST请求的数据
* @return 解密后的原文
*/
public String decryptMsg(String msgSignature, String timestamp, String nonce, String postData) {
// 提取密文
Object[] encrypt = XmlUtil.extract(postData);
// 验证安全签名
BizMsgCryptUtil.verify(messageValidToken, msgSignature, timestamp, nonce, encrypt[1].toString());
// 解密
return BizMsgCryptUtil.decrypt(appid, encrypt[1].toString(), aesKey, RANDOM_BYTES_POS);
}
}
字节跳动(抖音)
package com.honyee.app.utils.crypt;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* 抖音解密工具
*
* @author honyee
*/
public class BytedanceBizMsgCrypt {
// 解密时使用,这个值一般从各个平台提供的解密demo中获取获取
private static final int RANDOM_BYTES_POS = 32;
final byte[] aesKey;
final String messageValidToken;
final String appid;
/**
* 构造函数
*
* @param messageValidToken 公众平台上,开发者设置的 message_valid_token
* @param encodingAesKey 公众平台上,开发者设置的 message_encode_decode_key
* @param appid 公众平台appid
*/
public BytedanceBizMsgCrypt(String messageValidToken, String encodingAesKey, String appid) {
if (encodingAesKey.length() != 43) {
throw new RuntimeException("初始化失败:key长度不是43位");
}
this.messageValidToken = messageValidToken;
this.appid = appid;
aesKey = Base64.getDecoder().decode(encodingAesKey + "=");
}
/**
* 对明文进行加密.
* <p>
* 加密后无法解密,加密pos=32,解密需要16,没找到哪里改
*
* @param text 需要加密的明文
* @return 加密后base64编码的字符串
*/
public BizMsgEncryptResult encrypt(String text, String timestamp, String nonce) {
String randomStr = BizMsgCryptUtil.getRandomStr();
// 加密
String encryptData = BizMsgCryptUtil.encrypt(appid, aesKey, randomStr, text);
// 生成安全签名
if ("".equals(timestamp)) {
timestamp = Long.toString(System.currentTimeMillis());
}
String signature = BizMsgCryptUtil.getSignature(messageValidToken, timestamp, nonce, encryptData);
return new BizMsgEncryptResult()
.timestamp(timestamp)
.nonce(nonce)
.signature(signature)
.randomStr(randomStr)
.encryptData(encryptData);
}
/**
* 对密文进行解密 用于移动端数据解密
*
* @param encryptedData 密文
* @param iv 向量
* @param sessionKey 密钥
* @return 明文
*/
public static String decrypt(String encryptedData, String iv, String sessionKey) {
byte[] original = BizMsgCryptUtil.decrypt(encryptedData, iv, sessionKey);
if (null != original && original.length > 0) {
return new String(original, StandardCharsets.UTF_8);
}
return null;
}
/**
* 检验消息的真实性,并且获取解密后的明文. 用于服务端推送消息解密
*
* @param msgSignature 签名串,对应URL参数的msg_signature
* @param timestamp 时间戳,对应URL参数的timestamp
* @param nonce 随机串,对应URL参数的nonce
* @param postData 密文,对应POST请求的数据
* @return 解密后的原文
*/
public String decryptMsg(String msgSignature, String timestamp, String nonce, String postData) {
// 验证安全签名
BizMsgCryptUtil.verify(messageValidToken, msgSignature, timestamp, nonce, postData);
// 解密
return BizMsgCryptUtil.decrypt(appid, postData, aesKey, RANDOM_BYTES_POS);
}
}
百度(智能小程序)
package com.honyee.app.utils.crypt;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
/**
* 百度解密工具
*
* @author honyee
*/
public class BaiduBizMsgCrypt {
// 解密时使用,这个值一般从各个平台提供的解密demo中获取获取
private static final int RANDOM_BYTES_POS = 16;
final byte[] aesKey;
final String messageValidToken;
final String appid;
/**
* 构造函数
*
* @param messageValidToken 公众平台上,开发者设置的 message_valid_token
* @param encodingAesKey 公众平台上,开发者设置的 message_encode_decode_key
* @param appid 公众平台appid
*/
public BaiduBizMsgCrypt(String messageValidToken, String encodingAesKey, String appid) {
if (encodingAesKey.length() != 43) {
throw new RuntimeException("初始化失败:key长度不是43位");
}
this.messageValidToken = messageValidToken;
this.appid = appid;
aesKey = Base64.getDecoder().decode(encodingAesKey + "=");
}
/**
* 对明文进行加密.
*
* @param text 公众平台待回复用户的消息,xml格式的字符串
* @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
* @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
*/
public String encryptMsg(String text, String timeStamp, String nonce) {
return null;
}
/**
* 对密文进行解密 用于移动端数据解密
*
* @param encryptedData 密文
* @param iv 向量
* @param sessionKey 密钥
* @return 明文
*/
public static String decrypt(String encryptedData, String iv, String sessionKey) {
byte[] original = BizMsgCryptUtil.decrypt(encryptedData, iv, sessionKey);
if (null != original && original.length > 0) {
try {
// 去除补位字符
byte[] bytes = PKCS7.decode(original);
// ==== 分离
int pos = RANDOM_BYTES_POS;
// 分离16位随机字符串,网络字节序和ClientId
byte[] networkOrder = Arrays.copyOfRange(bytes, pos, pos + 4);
int contentLength = BizMsgCryptUtil.recoverNetworkBytesOrder(networkOrder);
return new String(Arrays.copyOfRange(bytes, pos + 4, pos + 4 + contentLength), StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("解密失败:" + e.getMessage());
}
}
return null;
}
/**
* 检验消息的真实性,并且获取解密后的明文. 用于服务端推送消息解密
* <ol>
* <li>利用收到的密文生成安全签名,进行签名验证</li>
* <li>若验证通过,则提取xml中的加密消息</li>
* <li>对消息进行解密</li>
* </ol>
*
* @param msgSignature 签名串,对应URL参数的msg_signature
* @param timestamp 时间戳,对应URL参数的timestamp
* @param nonce 随机串,对应URL参数的nonce
* @param postData 密文,对应POST请求的数据
* @return 解密后的原文
*/
public String decryptMsg(String msgSignature, String timestamp, String nonce, String postData) {
// 验证安全签名
BizMsgCryptUtil.verify(messageValidToken, msgSignature, timestamp, nonce, postData);
// 解密
return BizMsgCryptUtil.decrypt(appid, postData, aesKey, RANDOM_BYTES_POS);
}
/**
* 验证
*/
public void verify(String msgSignature, String timestamp, String nonce, String encryptData) {
BizMsgCryptUtil.verify(messageValidToken, msgSignature, timestamp, nonce, encryptData);
}
}
小红书
package com.honyee.app.utils.crypt;
import java.nio.charset.StandardCharsets;
/**
* 百度解密工具
*
* @author honyee
*/
public class XhsBizMsgCrypt {
// 解密时使用,这个值一般从各个平台提供的解密demo中获取获取
private static final int RANDOM_BYTES_POS = 16;
private final byte[] aesKey;
private final String messageValidToken;
private final String appid;
/**
* 构造函数
*
* @param messageValidToken 公众平台上,开发者设置的 message_valid_token
* @param encodingAesKey 公众平台上,开发者设置的 message_encode_decode_key
* @param appid 公众平台appid
*/
public XhsBizMsgCrypt(String messageValidToken, String encodingAesKey, String appid) {
this.aesKey = java.util.Base64.getDecoder().decode(encodingAesKey + "=");
;
this.messageValidToken = messageValidToken;
this.appid = appid;
}
/**
* 检验消息的真实性,并且获取解密后的明文. 用于服务端推送消息解密
* <ol>
* <li>利用收到的密文生成安全签名,进行签名验证</li>
* <li>若验证通过,则提取xml中的加密消息</li>
* <li>对消息进行解密</li>
* </ol>
*
* @param msgSignature 签名串,对应URL参数的msg_signature
* @param timestamp 时间戳,对应URL参数的timestamp
* @param nonce 随机串,对应URL参数的nonce
* @param postData 密文,对应POST请求的数据
* @return 解密后的原文
*/
public String decryptMsg(String msgSignature, String timestamp, String nonce, String postData) {
// 验证安全签名
BizMsgCryptUtil.verify(messageValidToken, msgSignature, timestamp, nonce, postData);
// 解密
return BizMsgCryptUtil.decrypt(appid, postData, aesKey, RANDOM_BYTES_POS);
}
/**
* 对密文进行解密
*
* @param encryptedData 密文
* @param sessionKey session2Code接口获取到的sessionKey
* @param iv 向量
* @return 解密后的明文
*/
public static String decrypt(String encryptedData, String sessionKey, String iv) {
byte[] original = BizMsgCryptUtil.decrypt(encryptedData, iv, sessionKey);
if (null != original && original.length > 0) {
try {
// 去除补位字符
byte[] bytes = PKCS7.decode(original);
// ==== 分离
//int pos = RANDOM_BYTES_POS;
// 分离16位随机字符串,网络字节序和ClientId
//byte[] networkOrder = Arrays.copyOfRange(bytes, pos, pos + 4);
//int contentLength = BizMsgCryptUtil.recoverNetworkBytesOrder(networkOrder);
//return new String(Arrays.copyOfRange(bytes, pos + 4, pos + 4 + contentLength), StandardCharsets.UTF_8);
return new String(bytes, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("解密失败:" + e.getMessage());
}
}
return null;
}
}