MessageUtil.java

package com.zone.weixin4j.util;

import com.zone.weixin4j.base64.Base64;
import com.zone.weixin4j.exception.WeixinException;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;

/**
 * 消息工具类
 * 
 * @className MessageUtil
 * @author jinyu(foxinmy@gmail.com)
 * @date 2014年10月31日
 * @since JDK 1.6
 * @see
 */
public final class MessageUtil {
	/**
	 * 验证微信签名
	 * 
	 * @param signature
	 *            微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数
	 * @return 开发者通过检验signature对请求进行相关校验。若确认此次GET请求来自微信服务器
	 *         请原样返回echostr参数内容,则接入生效 成为开发者成功,否则接入失败
	 * @see <a
	 *      href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN">接入指南</a>
	 */
	public static String signature(String... para) {
		Arrays.sort(para);
		StringBuffer sb = new StringBuffer();
		for (String str : para) {
			sb.append(str);
		}
		return ServerToolkits.digestSHA1(sb.toString());
	}

	/**
	 * 对xml消息加密
	 * 
	 * @param appId
	 *            应用ID
	 * @param encodingAesKey
	 *            加密密钥
	 * @param xmlContent
	 *            原始消息体
	 * @return aes加密后的消息体
	 * @throws WeixinException
	 */
	public static String aesEncrypt(String appId, String encodingAesKey,
			String xmlContent) throws WeixinException {
		/**
		 * 其中,msg_encrypt=Base64_Encode(AES_Encrypt [random(16B)+ msg_len(4B) +
		 * msg + $AppId])
		 * 
		 * random(16B)为16字节的随机字符串;msg_len为msg长度,占4个字节(网络字节序),$AppId为公众账号的AppId
		 */
		byte[] randomBytes = ServerToolkits.getBytesUtf8(ServerToolkits
				.generateRandomString(16));
		byte[] xmlBytes = ServerToolkits.getBytesUtf8(xmlContent);
		int xmlLength = xmlBytes.length;
		byte[] orderBytes = new byte[4];
		orderBytes[3] = (byte) (xmlLength & 0xFF);
		orderBytes[2] = (byte) (xmlLength >> 8 & 0xFF);
		orderBytes[1] = (byte) (xmlLength >> 16 & 0xFF);
		orderBytes[0] = (byte) (xmlLength >> 24 & 0xFF);
		byte[] appidBytes = ServerToolkits.getBytesUtf8(appId);

		int byteLength = randomBytes.length + xmlLength + orderBytes.length
				+ appidBytes.length;
		// ... + pad: 使用自定义的填充方式对明文进行补位填充
		byte[] padBytes = PKCS7Encoder.encode(byteLength);
		// random + endian + xml + appid + pad 获得最终的字节流
		byte[] unencrypted = new byte[byteLength + padBytes.length];
		byteLength = 0;
		// src:源数组;srcPos:源数组要复制的起始位置;dest:目的数组;destPos:目的数组放置的起始位置;length:复制的长度
		System.arraycopy(randomBytes, 0, unencrypted, byteLength,
				randomBytes.length);
		byteLength += randomBytes.length;
		System.arraycopy(orderBytes, 0, unencrypted, byteLength,
				orderBytes.length);
		byteLength += orderBytes.length;
		System.arraycopy(xmlBytes, 0, unencrypted, byteLength, xmlBytes.length);
		byteLength += xmlBytes.length;
		System.arraycopy(appidBytes, 0, unencrypted, byteLength,
				appidBytes.length);
		byteLength += appidBytes.length;
		System.arraycopy(padBytes, 0, unencrypted, byteLength, padBytes.length);
		try {
			byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=");
			// 设置加密模式为AES的CBC模式
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			SecretKeySpec keySpec = new SecretKeySpec(aesKey, ServerToolkits.AES);
			IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
			cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
			// 加密
			byte[] encrypted = cipher.doFinal(unencrypted);
			// 使用BASE64对加密后的字符串进行编码
			// return Base64.encodeBase64String(encrypted);
			return Base64
					.encodeBase64String(encrypted);
		} catch (Exception e) {
			throw new WeixinException("-40006", "AES加密失败:" + e.getMessage());
		}
	}

	/**
	 * 对AES消息解密
	 * 
	 * @param appId
	 * @param encodingAesKey
	 *            aes加密的密钥
	 * @param encryptContent
	 *            加密的消息体
	 * @return 解密后的字符
	 * @throws WeixinException
	 */
	public static String aesDecrypt(String appId, String encodingAesKey,
			String encryptContent) throws WeixinException {
		byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=");
		byte[] original;
		try {
			// 设置解密模式为AES的CBC模式
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			SecretKeySpec key_spec = new SecretKeySpec(aesKey, ServerToolkits.AES);
			IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey,
					0, 16));
			cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
			// 使用BASE64对密文进行解码
			byte[] encrypted = Base64.decodeBase64(encryptContent);
			// 解密
			original = cipher.doFinal(encrypted);
		} catch (Exception e) {
			throw new WeixinException("-40007", "AES解密失败:" + e.getMessage());
		}
		String xmlContent, fromAppId;
		try {
			// 去除补位字符
			byte[] bytes = PKCS7Encoder.decode(original);
			/**
			 * AES加密的buf由16个字节的随机字符串、4个字节的msg_len(网络字节序)、msg和$AppId组成,
			 * 其中msg_len为msg的长度,$AppId为公众帐号的AppId
			 */
			// 获取表示xml长度的字节数组
			byte[] lengthByte = Arrays.copyOfRange(bytes, 16, 20);
			// 获取xml消息主体的长度(byte[]2int)
			// http://my.oschina.net/u/169390/blog/97495
			int xmlLength = lengthByte[3] & 0xff | (lengthByte[2] & 0xff) << 8
					| (lengthByte[1] & 0xff) << 16
					| (lengthByte[0] & 0xff) << 24;
			xmlContent = ServerToolkits.newStringUtf8(Arrays.copyOfRange(bytes, 20,
					20 + xmlLength));
			fromAppId = ServerToolkits.newStringUtf8(Arrays.copyOfRange(bytes,
					20 + xmlLength, bytes.length));
		} catch (Exception e) {
			throw new WeixinException("-40008", "xml内容不合法:" + e.getMessage());
		}
		// 校验appId是否一致
		if (appId != null && !fromAppId.trim().equals(appId)) {
			throw new WeixinException("-40005", "校验AppID失败,expect " + appId
					+ ",but actual is " + fromAppId);
		}
		return xmlContent;
	}
}