View Javadoc
1   package com.zone.weixin4j.util;
2   
3   import com.zone.weixin4j.base64.Base64;
4   import com.zone.weixin4j.exception.WeixinException;
5   
6   import javax.crypto.Cipher;
7   import javax.crypto.spec.IvParameterSpec;
8   import javax.crypto.spec.SecretKeySpec;
9   import java.util.Arrays;
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
29  	 *      href="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,
53  			String xmlContent) throws WeixinException {
54  		/**
55  		 * 其中,msg_encrypt=Base64_Encode(AES_Encrypt [random(16B)+ msg_len(4B) +
56  		 * msg + $AppId])
57  		 * 
58  		 * random(16B)为16字节的随机字符串;msg_len为msg长度,占4个字节(网络字节序),$AppId为公众账号的AppId
59  		 */
60  		byte[] randomBytes = ServerToolkits.getBytesUtf8(ServerToolkits
61  				.generateRandomString(16));
62  		byte[] xmlBytes = ServerToolkits.getBytesUtf8(xmlContent);
63  		int xmlLength = xmlBytes.length;
64  		byte[] orderBytes = new byte[4];
65  		orderBytes[3] = (byte) (xmlLength & 0xFF);
66  		orderBytes[2] = (byte) (xmlLength >> 8 & 0xFF);
67  		orderBytes[1] = (byte) (xmlLength >> 16 & 0xFF);
68  		orderBytes[0] = (byte) (xmlLength >> 24 & 0xFF);
69  		byte[] appidBytes = ServerToolkits.getBytesUtf8(appId);
70  
71  		int byteLength = randomBytes.length + xmlLength + orderBytes.length
72  				+ appidBytes.length;
73  		// ... + pad: 使用自定义的填充方式对明文进行补位填充
74  		byte[] padBytes = PKCS7Encoder.encode(byteLength);
75  		// random + endian + xml + appid + pad 获得最终的字节流
76  		byte[] unencrypted = new byte[byteLength + padBytes.length];
77  		byteLength = 0;
78  		// src:源数组;srcPos:源数组要复制的起始位置;dest:目的数组;destPos:目的数组放置的起始位置;length:复制的长度
79  		System.arraycopy(randomBytes, 0, unencrypted, byteLength,
80  				randomBytes.length);
81  		byteLength += randomBytes.length;
82  		System.arraycopy(orderBytes, 0, unencrypted, byteLength,
83  				orderBytes.length);
84  		byteLength += orderBytes.length;
85  		System.arraycopy(xmlBytes, 0, unencrypted, byteLength, xmlBytes.length);
86  		byteLength += xmlBytes.length;
87  		System.arraycopy(appidBytes, 0, unencrypted, byteLength,
88  				appidBytes.length);
89  		byteLength += appidBytes.length;
90  		System.arraycopy(padBytes, 0, unencrypted, byteLength, padBytes.length);
91  		try {
92  			byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=");
93  			// 设置加密模式为AES的CBC模式
94  			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
95  			SecretKeySpec keySpec = new SecretKeySpec(aesKey, ServerToolkits.AES);
96  			IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
97  			cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
98  			// 加密
99  			byte[] encrypted = cipher.doFinal(unencrypted);
100 			// 使用BASE64对加密后的字符串进行编码
101 			// return Base64.encodeBase64String(encrypted);
102 			return Base64
103 					.encodeBase64String(encrypted);
104 		} catch (Exception e) {
105 			throw new WeixinException("-40006", "AES加密失败:" + e.getMessage());
106 		}
107 	}
108 
109 	/**
110 	 * 对AES消息解密
111 	 * 
112 	 * @param appId
113 	 * @param encodingAesKey
114 	 *            aes加密的密钥
115 	 * @param encryptContent
116 	 *            加密的消息体
117 	 * @return 解密后的字符
118 	 * @throws WeixinException
119 	 */
120 	public static String aesDecrypt(String appId, String encodingAesKey,
121 			String encryptContent) throws WeixinException {
122 		byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=");
123 		byte[] original;
124 		try {
125 			// 设置解密模式为AES的CBC模式
126 			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
127 			SecretKeySpec key_spec = new SecretKeySpec(aesKey, ServerToolkits.AES);
128 			IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey,
129 					0, 16));
130 			cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
131 			// 使用BASE64对密文进行解码
132 			byte[] encrypted = Base64.decodeBase64(encryptContent);
133 			// 解密
134 			original = cipher.doFinal(encrypted);
135 		} catch (Exception e) {
136 			throw new WeixinException("-40007", "AES解密失败:" + e.getMessage());
137 		}
138 		String xmlContent, fromAppId;
139 		try {
140 			// 去除补位字符
141 			byte[] bytes = PKCS7Encoder.decode(original);
142 			/**
143 			 * AES加密的buf由16个字节的随机字符串、4个字节的msg_len(网络字节序)、msg和$AppId组成,
144 			 * 其中msg_len为msg的长度,$AppId为公众帐号的AppId
145 			 */
146 			// 获取表示xml长度的字节数组
147 			byte[] lengthByte = Arrays.copyOfRange(bytes, 16, 20);
148 			// 获取xml消息主体的长度(byte[]2int)
149 			// http://my.oschina.net/u/169390/blog/97495
150 			int xmlLength = lengthByte[3] & 0xff | (lengthByte[2] & 0xff) << 8
151 					| (lengthByte[1] & 0xff) << 16
152 					| (lengthByte[0] & 0xff) << 24;
153 			xmlContent = ServerToolkits.newStringUtf8(Arrays.copyOfRange(bytes, 20,
154 					20 + xmlLength));
155 			fromAppId = ServerToolkits.newStringUtf8(Arrays.copyOfRange(bytes,
156 					20 + xmlLength, bytes.length));
157 		} catch (Exception e) {
158 			throw new WeixinException("-40008", "xml内容不合法:" + e.getMessage());
159 		}
160 		// 校验appId是否一致
161 		if (appId != null && !fromAppId.trim().equals(appId)) {
162 			throw new WeixinException("-40005", "校验AppID失败,expect " + appId
163 					+ ",but actual is " + fromAppId);
164 		}
165 		return xmlContent;
166 	}
167 }