net.ymate.platform.module.wechat.support.MessageHelper.java Source code

Java tutorial

Introduction

Here is the source code for net.ymate.platform.module.wechat.support.MessageHelper.java

Source

/*
 * Copyright 2007-2107 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.ymate.platform.module.wechat.support;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import net.ymate.platform.commons.util.RuntimeUtils;
import net.ymate.platform.module.wechat.message.OutMessage;
import net.ymate.platform.module.wechat.message.in.InMessage;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * <p>
 * MessageHelper
 * </p>
 * <p/>
 * ?????????(UTF8?).
 * <ol>
 * <li>???</li>
 * <li>???????</li>
 * </ol>
 * java.security.InvalidKeyException:illegal Key Size
 * <ol>
 * <li>JCE???JDK7?
 * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
 * <li>??local_policy.jarUS_export_policy.jar?readme.txt</li>
 * <li>JREjar%JRE_HOME%\lib\security?</li>
 * <li>JDKjar%JDK_HOME%\jre\lib\security?</li>
 * </ol>
 * <p/>
 *
 * @author (suninformation@163.com)
 * @version 0.0.0
 *          <table style="border:1px solid gray;">
 *          <tr>
 *          <th width="100px">?</th><th width="100px"></th><th
 *          width="100px"></th><th width="100px"></th>
 *          </tr>
 *          <!--  Table ?? -->
 *          <tr>
 *          <td>0.0.0</td>
 *          <td></td>
 *          <td></td>
 *          <td>2014315?3:16:53</td>
 *          </tr>
 *          </table>
 */
public class MessageHelper {

    static Charset __CHARSET = Charset.forName("utf-8");

    static String __RANDOM_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    /**
     * @param protocolStr ???
     * @return ?????
     */
    public static InMessage parsingInMessage(String protocolStr) {
        XStream _xstream = XStreamHelper.createXStream(false);
        _xstream.ignoreUnknownElements();
        _xstream.processAnnotations(InMessage.class);
        return (InMessage) _xstream.fromXML(protocolStr);
    }

    public static String parsingOutMessage(OutMessage message) {
        XStream _xstream = XStreamHelper.createXStream(true);
        _xstream.processAnnotations(message.getClass());
        return _xstream.toXML(message);
    }

    //------------
    // ?/
    //------------

    public static EncryptMsg parsingEncryptMsg(String protocolStr) {
        XStream _xstream = XStreamHelper.createXStream(false);
        _xstream.ignoreUnknownElements();
        _xstream.processAnnotations(EncryptMsg.class);
        return (EncryptMsg) _xstream.fromXML(protocolStr);
    }

    /**
     * ?URL
     *
     * @param appId
     * @param encodingAesKey
     * @param token
     * @param msgSignature   ??
     * @param timeStamp      
     * @param nonce          ?
     * @param echoStr        ?
     * @return ?echostr
     * @throws AesException ??
     */
    @Deprecated
    public static String verifyUrl(String appId, String encodingAesKey, String token, String msgSignature,
            String timeStamp, String nonce, String echoStr) throws AesException {
        String signature = getSHA1(token, timeStamp, nonce, echoStr);
        if (!signature.equals(msgSignature)) {
            throw new AesException(AesException.ValidateSignatureError);
        }
        byte[] _aesKey = Base64.decodeBase64(encodingAesKey + "=");
        return decrypt(appId, _aesKey, echoStr);
    }

    /**
     * @param appId
     * @param encodingAesKey
     * @param token
     * @param messageStr
     * @return
     * @throws Exception
     */
    public static String encryptMessage(String appId, String encodingAesKey, String token, String messageStr)
            throws Exception {
        if (encodingAesKey.length() != 43) {
            throw new AesException(AesException.IllegalAesKey);
        }
        byte[] _aesKey = Base64.decodeBase64(encodingAesKey + "=");
        //
        String randomStr = RandomStringUtils.random(16, __RANDOM_CHARS);
        String nonce = RandomStringUtils.random(16, __RANDOM_CHARS);
        // 
        String encryptedXml = encrypt(appId, _aesKey, randomStr, messageStr);
        // ?
        String timeStamp = Long.toString(System.currentTimeMillis() / 1000);
        // ???
        String signature = getSHA1(token, timeStamp, nonce, encryptedXml);
        return new EncryptMsg(signature, timeStamp, nonce, encryptedXml).toXML();
    }

    /**
     * @param appId
     * @param encodingAesKey
     * @param token
     * @param protocolStr
     * @return
     * @throws Exception
     */
    public static String decryptMessage(String appId, String encodingAesKey, String token, String protocolStr)
            throws Exception {
        if (encodingAesKey.length() != 43) {
            throw new AesException(AesException.IllegalAesKey);
        }
        String _token = token;
        String _appId = appId;
        byte[] _aesKey = Base64.decodeBase64(encodingAesKey + "=");
        // ?app secret
        // ???
        EncryptMsg _encryptMsg = parsingEncryptMsg(protocolStr);
        // ???
        String signature = getSHA1(token, _encryptMsg.getTimeStamp(), _encryptMsg.getNonce(),
                _encryptMsg.getEncrypt());
        // URL???
        // System.out.println("URL??" + msg_sign);
        // System.out.println("??" + signature);
        if (!signature.equals(_encryptMsg.getMsgSignature())) {
            throw new AesException(AesException.ValidateSignatureError);
        }
        // 
        return decrypt(appId, _aesKey, _encryptMsg.getEncrypt());
    }

    static String encrypt(String appId, byte[] aesKey, String randomStr, String messageStr) throws AesException {
        ByteGroup byteCollector = new ByteGroup();
        byte[] randomStrBytes = randomStr.getBytes(__CHARSET);
        byte[] textBytes = messageStr.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 {
            // ?AESCBC?
            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.encodeBase64URLSafeString(encrypted);
            //
            return base64Encrypted;
        } catch (Exception e) {
            throw new AesException(AesException.EncryptAESError, RuntimeUtils.unwrapThrow(e));
        }
    }

    /**
     * .
     *
     * @param encryptText ?
     * @return 
     * @throws AesException aes
     */
    static String decrypt(String appId, byte[] aesKey, String encryptText) throws AesException {
        byte[] original;
        try {
            // ?AESCBC?
            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(encryptText);
            // 
            original = cipher.doFinal(encrypted);
        } catch (Exception e) {
            throw new AesException(AesException.DecryptAESError, RuntimeUtils.unwrapThrow(e));
        }

        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) {
            throw new AesException(AesException.IllegalBuffer, RuntimeUtils.unwrapThrow(e));
        }
        // appid??
        if (!from_appid.equals(appId)) {
            throw new AesException(AesException.ValidateAppidError);
        }
        return xmlContent;

    }

    /**
     * ?4?
     *
     * @param number
     */
    static byte[] getNetworkBytesOrder(int number) {
        byte[] orderBytes = new byte[4];
        orderBytes[3] = (byte) (number & 0xFF);
        orderBytes[2] = (byte) (number >> 8 & 0xFF);
        orderBytes[1] = (byte) (number >> 16 & 0xFF);
        orderBytes[0] = (byte) (number >> 24 & 0xFF);
        return orderBytes;
    }

    /**
     * 4?
     *
     * @param orderBytes
     * @return
     */
    static int recoverNetworkBytesOrder(byte[] orderBytes) {
        int sourceNumber = 0;
        for (int i = 0; i < 4; i++) {
            sourceNumber <<= 8;
            sourceNumber |= orderBytes[i] & 0xff;
        }
        return sourceNumber;
    }

    /**
     * SHA1???
     *
     * @param token     ?
     * @param timestamp 
     * @param nonce     ?
     * @param encrypt   
     * @return ??
     */
    static String getSHA1(String token, String timestamp, String nonce, String encrypt) {
        String[] array = new String[] { token, timestamp, nonce, encrypt };
        // ?
        Arrays.sort(array);
        // SHA1???
        return DigestUtils.shaHex(StringUtils.join(array, ""));
    }

    static class ByteGroup {
        ArrayList<Byte> byteContainer = new ArrayList<Byte>();

        public byte[] toBytes() {
            byte[] bytes = new byte[byteContainer.size()];
            for (int i = 0; i < byteContainer.size(); i++) {
                bytes[i] = byteContainer.get(i);
            }
            return bytes;
        }

        public ByteGroup addBytes(byte[] bytes) {
            for (byte b : bytes) {
                byteContainer.add(b);
            }
            return this;
        }

        public int size() {
            return byteContainer.size();
        }
    }

    @XStreamAlias("xml")
    static class EncryptMsg {

        @XStreamAlias("ToUserName")
        private String toUserName;

        @XStreamAlias("MsgSignature")
        private String msgSignature;

        @XStreamAlias("TimeStamp")
        private String timeStamp;

        @XStreamAlias("Nonce")
        private String nonce;

        @XStreamAlias("Encrypt")
        private String encrypt;

        public EncryptMsg() {
        }

        public EncryptMsg(String msgSignature, String timeStamp, String nonce, String encrypt) {
            this.msgSignature = msgSignature;
            this.timeStamp = timeStamp;
            this.nonce = nonce;
            this.encrypt = encrypt;
        }

        public String toXML() {
            XStream _xStream = XStreamHelper.createXStream(true);
            _xStream.processAnnotations(this.getClass());
            _xStream.ignoreUnknownElements();
            return _xStream.toXML(this);
        }

        public String getToUserName() {
            return toUserName;
        }

        public void setToUserName(String toUserName) {
            this.toUserName = toUserName;
        }

        public String getMsgSignature() {
            return msgSignature;
        }

        public void setMsgSignature(String msgSignature) {
            this.msgSignature = msgSignature;
        }

        public String getTimeStamp() {
            return timeStamp;
        }

        public void setTimeStamp(String timeStamp) {
            this.timeStamp = timeStamp;
        }

        public String getNonce() {
            return nonce;
        }

        public void setNonce(String nonce) {
            this.nonce = nonce;
        }

        public String getEncrypt() {
            return encrypt;
        }

        public void setEncrypt(String encrypt) {
            this.encrypt = encrypt;
        }
    }

    /**
     * ??PKCS7?.
     */
    static class PKCS7Encoder {

        static int BLOCK_SIZE = 32;

        /**
         * ?.
         *
         * @param count ???
         * @return ?
         */
        static byte[] encode(int count) {
            // ??
            int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
            if (amountToPad == 0) {
                amountToPad = BLOCK_SIZE;
            }
            // ?
            char padChr = chr(amountToPad);
            String tmp = new String();
            for (int index = 0; index < amountToPad; index++) {
                tmp += padChr;
            }
            return tmp.getBytes(__CHARSET);
        }

        /**
         * ??
         *
         * @param decrypted ?
         * @return ??
         */
        static byte[] decode(byte[] decrypted) {
            int pad = (int) decrypted[decrypted.length - 1];
            if (pad < 1 || pad > 32) {
                pad = 0;
            }
            return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
        }

        /**
         * ?ASCII??
         *
         * @param a ?
         * @return 
         */
        static char chr(int a) {
            byte target = (byte) (a & 0xFF);
            return (char) target;
        }

    }

    public static class AesException extends Exception {

        public final static int OK = 0;
        public final static int ValidateSignatureError = -40001;
        public final static int ParseXmlError = -40002;
        public final static int ComputeSignatureError = -40003;
        public final static int IllegalAesKey = -40004;
        public final static int ValidateAppidError = -40005;
        public final static int EncryptAESError = -40006;
        public final static int DecryptAESError = -40007;
        public final static int IllegalBuffer = -40008;

        private int code;

        private static String getMessage(int code) {
            switch (code) {
            case ValidateSignatureError:
                return "???";
            case ParseXmlError:
                return "xml?";
            case ComputeSignatureError:
                return "sha???";
            case IllegalAesKey:
                return "SymmetricKey?";
            case ValidateAppidError:
                return "appid";
            case EncryptAESError:
                return "aes";
            case DecryptAESError:
                return "aes";
            case IllegalBuffer:
                return "?buffer?";
            default:
                return null; // cannot be
            }
        }

        public int getCode() {
            return code;
        }

        AesException(int code) {
            super(getMessage(code));
            this.code = code;
        }

        AesException(int code, Throwable cause) {
            super(getMessage(code), cause);
            this.code = code;
        }

    }

}