View Javadoc
1   package com.foxinmy.weixin4j.util;
2   
3   import java.util.Arrays;
4   
5   import javax.crypto.Cipher;
6   import javax.crypto.spec.IvParameterSpec;
7   import javax.crypto.spec.SecretKeySpec;
8   
9   import com.foxinmy.weixin4j.base64.Base64;
10  
11  /**
12   * 消息工具类
13   *
14   * @className MessageUtil
15   * @author jinyu(foxinmy@gmail.com)
16   * @date 2014年10月31日
17   * @since JDK 1.6
18   * @see
19   */
20  public final class MessageUtil {
21      /**
22       * 验证微信签名
23       *
24       * @param signature
25       *            微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数
26       * @return 开发者通过检验signature对请求进行相关校验。若确认此次GET请求来自微信服务器
27       *         请原样返回echostr参数内容,则接入生效 成为开发者成功,否则接入失败
28       * @see <a href=
29       *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN">接入指南</a>
30       */
31      public static String signature(String... para) {
32          Arrays.sort(para);
33          StringBuffer sb = new StringBuffer();
34          for (String str : para) {
35              sb.append(str);
36          }
37          return ServerToolkits.digestSHA1(sb.toString());
38      }
39  
40      /**
41       * 对xml消息加密
42       *
43       * @param appId
44       *            应用ID
45       * @param encodingAesKey
46       *            加密密钥
47       * @param xmlContent
48       *            原始消息体
49       * @return aes加密后的消息体
50       * @throws WeixinException
51       */
52      public static String aesEncrypt(String appId, String encodingAesKey, String xmlContent) {
53          /**
54           * 其中,msg_encrypt=Base64_Encode(AES_Encrypt [random(16B)+ msg_len(4B) +
55           * msg + $AppId])
56           *
57           * random(16B)为16字节的随机字符串;msg_len为msg长度,占4个字节(网络字节序),$AppId为公众账号的AppId
58           */
59          byte[] randomBytes = ServerToolkits.getBytesUtf8(ServerToolkits.generateRandomString(16));
60          byte[] xmlBytes = ServerToolkits.getBytesUtf8(xmlContent);
61          int xmlLength = xmlBytes.length;
62          byte[] orderBytes = new byte[4];
63          orderBytes[3] = (byte) (xmlLength & 0xFF);
64          orderBytes[2] = (byte) (xmlLength >> 8 & 0xFF);
65          orderBytes[1] = (byte) (xmlLength >> 16 & 0xFF);
66          orderBytes[0] = (byte) (xmlLength >> 24 & 0xFF);
67          byte[] appidBytes = ServerToolkits.getBytesUtf8(appId);
68  
69          int byteLength = randomBytes.length + xmlLength + orderBytes.length + appidBytes.length;
70          // ... + pad: 使用自定义的填充方式对明文进行补位填充
71          byte[] padBytes = PKCS7Encoder.encode(byteLength);
72          // random + endian + xml + appid + pad 获得最终的字节流
73          byte[] unencrypted = new byte[byteLength + padBytes.length];
74          byteLength = 0;
75          // src:源数组;srcPos:源数组要复制的起始位置;dest:目的数组;destPos:目的数组放置的起始位置;length:复制的长度
76          System.arraycopy(randomBytes, 0, unencrypted, byteLength, randomBytes.length);
77          byteLength += randomBytes.length;
78          System.arraycopy(orderBytes, 0, unencrypted, byteLength, orderBytes.length);
79          byteLength += orderBytes.length;
80          System.arraycopy(xmlBytes, 0, unencrypted, byteLength, xmlBytes.length);
81          byteLength += xmlBytes.length;
82          System.arraycopy(appidBytes, 0, unencrypted, byteLength, appidBytes.length);
83          byteLength += appidBytes.length;
84          System.arraycopy(padBytes, 0, unencrypted, byteLength, padBytes.length);
85          try {
86              byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=");
87              // 设置加密模式为AES的CBC模式
88              Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
89              SecretKeySpec keySpec = new SecretKeySpec(aesKey, ServerToolkits.AES);
90              IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
91              cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
92              // 加密
93              byte[] encrypted = cipher.doFinal(unencrypted);
94              // 使用BASE64对加密后的字符串进行编码
95              // return Base64.encodeBase64String(encrypted);
96              return Base64.encodeBase64String(encrypted);
97          } catch (Exception e) {
98              throw new RuntimeException("-40006,AES加密失败:" + e.getMessage());
99          }
100     }
101 
102     /**
103      * 对AES消息解密
104      *
105      * @param appId
106      * @param encodingAesKey
107      *            aes加密的密钥
108      * @param encryptContent
109      *            加密的消息体
110      * @return 解密后的字符
111      * @throws WeixinException
112      */
113     public static String aesDecrypt(String appId, String encodingAesKey, String encryptContent) {
114         byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=");
115         byte[] original;
116         try {
117             // 设置解密模式为AES的CBC模式
118             Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
119             SecretKeySpec key_spec = new SecretKeySpec(aesKey, ServerToolkits.AES);
120             IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
121             cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
122             // 使用BASE64对密文进行解码
123             byte[] encrypted = Base64.decodeBase64(encryptContent);
124             // 解密
125             original = cipher.doFinal(encrypted);
126         } catch (Exception e) {
127             throw new RuntimeException("-40007,AES解密失败:" + e.getMessage());
128         }
129         String xmlContent, fromAppId;
130         try {
131             // 去除补位字符
132             byte[] bytes = PKCS7Encoder.decode(original);
133             /**
134              * AES加密的buf由16个字节的随机字符串、4个字节的msg_len(网络字节序)、msg和$AppId组成,
135              * 其中msg_len为msg的长度,$AppId为公众帐号的AppId
136              */
137             // 获取表示xml长度的字节数组
138             byte[] lengthByte = Arrays.copyOfRange(bytes, 16, 20);
139             // 获取xml消息主体的长度(byte[]2int)
140             // http://my.oschina.net/u/169390/blog/97495
141             int xmlLength = lengthByte[3] & 0xff | (lengthByte[2] & 0xff) << 8 | (lengthByte[1] & 0xff) << 16
142                     | (lengthByte[0] & 0xff) << 24;
143             xmlContent = ServerToolkits.newStringUtf8(Arrays.copyOfRange(bytes, 20, 20 + xmlLength));
144             fromAppId = ServerToolkits.newStringUtf8(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length));
145         } catch (Exception e) {
146             throw new RuntimeException("-40008,xml内容不合法:" + e.getMessage());
147         }
148         // 校验appId是否一致
149         if (appId != null && !fromAppId.trim().equals(appId)) {
150             throw new RuntimeException("-40005,校验AppID失败,expect " + appId + ",but actual is " + fromAppId);
151         }
152         return xmlContent;
153     }
154 }