/*** 对公众平台发送给公众账号的消息加解密示例代码.
*
* @copyright Copyright (c) 1998-2014 Tencent Inc.*/
//------------------------------------------------------------------------
/*** 针对org.apache.commons.codec.binary.Base64,
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
*/
packagewebapp.ext.wechat;importorg.apache.commons.codec.binary.Base64;importjavax.crypto.Cipher;importjavax.crypto.spec.IvParameterSpec;importjavax.crypto.spec.SecretKeySpec;importjava.nio.charset.Charset;importjava.util.Arrays;importjava.util.Random;/*** 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串).
*
*
第三方回复加密消息给公众平台*
第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。*
* 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
*
*
在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:*http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
*
下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt*
如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件*
如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件*
*/public classWXBizMsgCrypt {static Charset CHARSET = Charset.forName("utf-8");
Base64 base64= newBase64();byte[] aesKey;
String token;
String appId;/*** 构造函数
*@paramtoken 公众平台上,开发者设置的token
*@paramencodingAesKey 公众平台上,开发者设置的EncodingAESKey
*@paramappId 公众平台appid
*
*@throwsAesException 执行失败,请查看该异常的错误码和具体的错误信息*/
public WXBizMsgCrypt(String token, String encodingAesKey, String appId) throwsAesException {if (encodingAesKey.length() != 43) {throw newAesException(AesException.IllegalAesKey);
}this.token =token;this.appId =appId;
aesKey= Base64.decodeBase64(encodingAesKey + "=");
}//生成4个字节的网络字节序
byte[] getNetworkBytesOrder(intsourceNumber) {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);returnorderBytes;
}//还原4个字节的网络字节序
int recoverNetworkBytesOrder(byte[] orderBytes) {int sourceNumber = 0;for (int i = 0; i < 4; i++) {
sourceNumber<<= 8;
sourceNumber|= orderBytes[i] & 0xff;
}returnsourceNumber;
}//随机生成16位字符串
String getRandomStr() {
String base= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random= newRandom();
StringBuffer sb= newStringBuffer();for (int i = 0; i < 16; i++) {int number =random.nextInt(base.length());
sb.append(base.charAt(number));
}returnsb.toString();
}/*** 对明文进行加密.
*
*@paramtext 需要加密的明文
*@return加密后base64编码的字符串
*@throwsAesException aes加密失败*/String encrypt(String randomStr, String text)throwsAesException {
ByteGroup byteCollector= newByteGroup();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 =PKCS7Encoder.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对加密后的字符串进行编码
String base64Encrypted =base64.encodeToString(encrypted);returnbase64Encrypted;
}catch(Exception e) {
e.printStackTrace();throw newAesException(AesException.EncryptAESError);
}
}/*** 对密文进行解密.
*
*@paramtext 需要解密的密文
*@return解密得到的明文
*@throwsAesException aes解密失败*/String decrypt(String text)throwsAesException {byte[] original;try{//设置解密模式为AES的CBC模式
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec key_spec= new SecretKeySpec(aesKey, "AES");
IvParameterSpec iv= new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);//使用BASE64对密文进行解码
byte[] encrypted =Base64.decodeBase64(text);//解密
original =cipher.doFinal(encrypted);
}catch(Exception e) {
e.printStackTrace();throw newAesException(AesException.DecryptAESError);
}
String xmlContent, from_appid;try{//去除补位字符
byte[] bytes =PKCS7Encoder.decode(original);//分离16位随机字符串,网络字节序和AppId
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);int xmlLength =recoverNetworkBytesOrder(networkOrder);
xmlContent= new String(Arrays.copyOfRange(bytes, 20, 20 +xmlLength), CHARSET);
from_appid= new String(Arrays.copyOfRange(bytes, 20 +xmlLength, bytes.length),
CHARSET);
}catch(Exception e) {
e.printStackTrace();throw newAesException(AesException.IllegalBuffer);
}//appid不相同的情况
if (!from_appid.equals(appId)) {throw newAesException(AesException.ValidateAppidError);
}returnxmlContent;
}/*** 将公众平台回复用户的消息加密打包.
*
*
对要发送的消息进行AES-CBC加密*
生成安全签名*
将消息密文和安全签名打包成xml格式*
*
*@paramreplyMsg 公众平台待回复用户的消息,xml格式的字符串
*@paramtimeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
*@paramnonce 随机串,可以自己生成,也可以用URL参数的nonce
*
*@return加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
*@throwsAesException 执行失败,请查看该异常的错误码和具体的错误信息*/
public String encryptMsg(String replyMsg, String timeStamp, String nonce) throwsAesException {//加密
String encrypt =encrypt(getRandomStr(), replyMsg);//生成安全签名
if (timeStamp == "") {
timeStamp=Long.toString(System.currentTimeMillis());
}
String signature=SHA1.getSHA1(token, timeStamp, nonce, encrypt);//System.out.println("发送给平台的签名是: " + signature[1].toString());//生成发送的xml
String result =XMLParse.generate(encrypt, signature, timeStamp, nonce);returnresult;
}/*** 检验消息的真实性,并且获取解密后的明文.
*
*
利用收到的密文生成安全签名,进行签名验证*
若验证通过,则提取xml中的加密消息*
对消息进行解密*
*
*@parammsgSignature 签名串,对应URL参数的msg_signature
*@paramtimeStamp 时间戳,对应URL参数的timestamp
*@paramnonce 随机串,对应URL参数的nonce
*@parampostData 密文,对应POST请求的数据
*
*@return解密后的原文
*@throwsAesException 执行失败,请查看该异常的错误码和具体的错误信息*/
publicString decryptMsg(String msgSignature, String timeStamp, String nonce, String postData)throwsAesException {//密钥,公众账号的app secret//提取密文
Object[] encrypt =XMLParse.extract(postData);//验证安全签名
String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString());//和URL中的签名比较是否相等//System.out.println("第三方收到URL中的签名:" + msg_sign);//System.out.println("第三方校验签名:" + signature);
if (!signature.equals(msgSignature)) {throw newAesException(AesException.ValidateSignatureError);
}//解密
String result = decrypt(encrypt[1].toString());returnresult;
}/*** 验证URL
*@parammsgSignature 签名串,对应URL参数的msg_signature
*@paramtimeStamp 时间戳,对应URL参数的timestamp
*@paramnonce 随机串,对应URL参数的nonce
*@paramechoStr 随机串,对应URL参数的echostr
*
*@return解密之后的echostr
*@throwsAesException 执行失败,请查看该异常的错误码和具体的错误信息*/
publicString verifyUrl(String msgSignature, String timeStamp, String nonce, String echoStr)throwsAesException {
String signature=SHA1.getSHA1(token, timeStamp, nonce, echoStr);if (!signature.equals(msgSignature)) {throw newAesException(AesException.ValidateSignatureError);
}
String result=decrypt(echoStr);returnresult;
}
}