47a1e46122420c96a5d4df0945a39d6cbd5af5d4.svn-base 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /**
  2. * 对公众平台发送给公众账号的消息加解密示例代码.
  3. *
  4. * @copyright Copyright (c) 1998-2014 Tencent Inc.
  5. */
  6. // ------------------------------------------------------------------------
  7. /**
  8. * 针对org.apache.commons.codec.binary.Base64,
  9. * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
  10. * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
  11. */
  12. package com.yihu.utils.aes;
  13. import java.nio.charset.Charset;
  14. import java.util.Arrays;
  15. import java.util.HashMap;
  16. import java.util.Map;
  17. import java.util.Random;
  18. import javax.crypto.Cipher;
  19. import javax.crypto.spec.IvParameterSpec;
  20. import javax.crypto.spec.SecretKeySpec;
  21. import org.apache.commons.codec.binary.Base64;
  22. /**
  23. * 提供消息的加解密接口(UTF8编码的字符串).
  24. * <ol>
  25. * <li>第三方发送到开放平台的消息进行加密,对收到开发平台的消息验证消息的安全性,并对消息进行解密。</li>
  26. * </ol>
  27. * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
  28. * <ol>
  29. * <li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:
  30. * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
  31. * <li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
  32. * <li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
  33. * <li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
  34. * </ol>
  35. */
  36. public class MsgCrypt {
  37. static Charset CHARSET = Charset.forName("utf-8");
  38. Base64 base64 = new Base64();
  39. byte[] aesKey;
  40. String appId;
  41. /**
  42. * 构造函数
  43. * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
  44. * @param appId 公众平台appid
  45. *
  46. * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
  47. */
  48. public MsgCrypt(String encodingAesKey, String appId) throws AesException {
  49. if (encodingAesKey.length() != 43) {
  50. throw new AesException(AesException.IllegalAesKey);
  51. }
  52. this.appId = appId;
  53. aesKey = Base64.decodeBase64(encodingAesKey + "=");
  54. }
  55. // 生成4个字节的网络字节序
  56. byte[] getNetworkBytesOrder(int sourceNumber) {
  57. byte[] orderBytes = new byte[4];
  58. orderBytes[3] = (byte) (sourceNumber & 0xFF);
  59. orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
  60. orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
  61. orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
  62. return orderBytes;
  63. }
  64. // 还原4个字节的网络字节序
  65. int recoverNetworkBytesOrder(byte[] orderBytes) {
  66. int sourceNumber = 0;
  67. for (int i = 0; i < 4; i++) {
  68. sourceNumber <<= 8;
  69. sourceNumber |= orderBytes[i] & 0xff;
  70. }
  71. return sourceNumber;
  72. }
  73. // 随机生成16位字符串
  74. String getRandomStr() {
  75. String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  76. Random random = new Random();
  77. StringBuffer sb = new StringBuffer();
  78. for (int i = 0; i < 16; i++) {
  79. int number = random.nextInt(base.length());
  80. sb.append(base.charAt(number));
  81. }
  82. return sb.toString();
  83. }
  84. /**
  85. * 对明文进行加密.
  86. *
  87. * @param text 需要加密的明文
  88. * @return 加密后base64编码的字符串
  89. * @throws AesException aes加密失败
  90. */
  91. String encrypt(String randomStr, String text) throws AesException {
  92. ByteGroup byteCollector = new ByteGroup();
  93. byte[] randomStrBytes = randomStr.getBytes(CHARSET);
  94. byte[] textBytes = text.getBytes(CHARSET);
  95. byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
  96. byte[] appidBytes = appId.getBytes(CHARSET);
  97. // randomStr + networkBytesOrder + text + appid
  98. byteCollector.addBytes(randomStrBytes);
  99. byteCollector.addBytes(networkBytesOrder);
  100. byteCollector.addBytes(textBytes);
  101. byteCollector.addBytes(appidBytes);
  102. // ... + pad: 使用自定义的填充方式对明文进行补位填充
  103. byte[] padBytes = PKCS7Encoder.encode(byteCollector.size());
  104. byteCollector.addBytes(padBytes);
  105. // 获得最终的字节流, 未加密
  106. byte[] unencrypted = byteCollector.toBytes();
  107. try {
  108. // 设置加密模式为AES的CBC模式
  109. Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  110. SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
  111. IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
  112. cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
  113. // 加密
  114. byte[] encrypted = cipher.doFinal(unencrypted);
  115. // 使用BASE64对加密后的字符串进行编码
  116. String base64Encrypted = base64.encodeToString(encrypted);
  117. return base64Encrypted;
  118. } catch (Exception e) {
  119. e.printStackTrace();
  120. throw new AesException(AesException.EncryptAESError);
  121. }
  122. }
  123. /**
  124. * 对密文进行解密.
  125. *
  126. * @param text 需要解密的密文
  127. * @return 解密得到的明文
  128. * @throws AesException aes解密失败
  129. */
  130. String decrypt(String text) throws AesException {
  131. byte[] original;
  132. try {
  133. // 设置解密模式为AES的CBC模式
  134. Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  135. SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
  136. IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
  137. cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
  138. // 使用BASE64对密文进行解码
  139. byte[] encrypted = Base64.decodeBase64(text);
  140. // 解密
  141. original = cipher.doFinal(encrypted);
  142. } catch (Exception e) {
  143. e.printStackTrace();
  144. throw new AesException(AesException.DecryptAESError);
  145. }
  146. String xmlContent, from_appid;
  147. try {
  148. // 去除补位字符
  149. byte[] bytes = PKCS7Encoder.decode(original);
  150. // 分离16位随机字符串,网络字节序和AppId
  151. byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
  152. int xmlLength = recoverNetworkBytesOrder(networkOrder);
  153. xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
  154. from_appid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
  155. CHARSET);
  156. } catch (Exception e) {
  157. e.printStackTrace();
  158. throw new AesException(AesException.IllegalBuffer);
  159. }
  160. // appid不相同的情况
  161. if (!from_appid.equals(appId)) {
  162. throw new AesException(AesException.ValidateAppidError);
  163. }
  164. return xmlContent;
  165. }
  166. /**
  167. * 将消息加密打包.
  168. * <ol>
  169. * <li>对要发送的消息进行AES-CBC加密</li>
  170. * <li>生成安全签名</li>
  171. * <li>将消息密文和安全签名打包成xml格式</li>
  172. * </ol>
  173. *
  174. * @param replyMsg 消息,JSON格式的字符串
  175. * @param timestamp 时间戳,可以自己生成,也可以用URL参数的timestamp
  176. *
  177. * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, encrypt的MAP
  178. * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
  179. */
  180. public Map<String,String> encryptMsg(String replyMsg, String timestamp) throws AesException {
  181. // 加密
  182. String encrypt = encrypt(getRandomStr(), replyMsg);
  183. // 生成安全签名
  184. if (timestamp == "") {
  185. timestamp = Long.toString(System.currentTimeMillis());
  186. }
  187. String signature = SHA1.getSHA1(timestamp, encrypt);
  188. Map<String,String> result=new HashMap<String,String>();
  189. result.put("encrypt", encrypt);
  190. result.put("sign", signature);
  191. result.put("timestamp", timestamp);
  192. result.put("appId", appId);
  193. return result;
  194. }
  195. /**
  196. * 检验消息的真实性,并且获取解密后的明文.
  197. * <ol>
  198. * <li>利用收到的密文生成安全签名,进行签名验证</li>
  199. * <li>若验证通过,则提取xml中的加密消息</li>
  200. * <li>对消息进行解密</li>
  201. * </ol>
  202. *
  203. * @param msgSignature 签名串,对应URL参数的sign
  204. * @param timestamp 时间戳,对应URL参数的timestamp
  205. * @param postData 密文,对应encrypt
  206. *
  207. * @return 解密后的原文
  208. * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
  209. */
  210. public String decryptMsg(String msgSignature, String timestamp, String postData)
  211. throws AesException {
  212. // 验证安全签名
  213. String signature = SHA1.getSHA1(timestamp,postData);
  214. // 和URL中的签名比较是否相等
  215. // System.out.println("第三方收到URL中的签名:" + msg_sign);
  216. // System.out.println("第三方校验签名:" + signature);
  217. if (!signature.equals(msgSignature)) {
  218. throw new AesException(AesException.ValidateSignatureError);
  219. }
  220. // 解密
  221. String result = decrypt(postData);
  222. return result;
  223. }
  224. /**
  225. * 验证URL
  226. * @param msgSignature 签名串,对应URL参数的msg_signature
  227. * @param timestamp 时间戳,对应URL参数的timestamp
  228. * @param echoStr 随机串,对应URL参数的echostr
  229. *
  230. * @return 解密之后的echostr
  231. * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
  232. */
  233. public String verifyUrl(String msgSignature, String timestamp, String echoStr)
  234. throws AesException {
  235. String signature = SHA1.getSHA1(timestamp, echoStr);
  236. if (!signature.equals(msgSignature)) {
  237. throw new AesException(AesException.ValidateSignatureError);
  238. }
  239. String result = decrypt(echoStr);
  240. return result;
  241. }
  242. }