Java tutorial
/* * This file Copyright (c) 2016. Walle. * (http://www.wallellen.com). All rights reserved. * * * This file is dual-licensed under both the * Walle Agreement (WA) and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or WA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Walle Agreement (WA), this file * and the accompanying materials are made available under the * terms of the WA which accompanies this distribution, and * is available at http://www.wallellen.com/agreement.html * * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER */ // ------------------------------------------------------------------------ /** * org.apache.commons.codec.binary.Base64 * ?commons-codec-1.9commons-codec-1.8 * ?http://commons.apache.org/proper/commons-codec/download_codec.cgi */ package com.wallellen.wechat.common.util.crypto; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.StringReader; import java.nio.charset.Charset; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; import java.util.SortedMap; import java.util.TreeMap; public class WxCryptUtil { private static final Base64 base64 = new Base64(); private static final Charset CHARSET = Charset.forName("utf-8"); private static final String XML_TEMPLATE = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n" + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" + "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" + "</xml>"; private static final ThreadLocal<DocumentBuilder> builderLocal = new ThreadLocal<DocumentBuilder>() { @Override protected DocumentBuilder initialValue() { try { return DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException exc) { throw new IllegalArgumentException(exc); } } }; protected byte[] aesKey; protected String token; protected String appidOrCorpid; public WxCryptUtil() { super(); } /** * * * @param token ??token * @param encodingAesKey ??EncodingAESKey * @param appidOrCorpid ?appid/corpid */ public WxCryptUtil(String token, String encodingAesKey, String appidOrCorpid) { this.token = token; this.appidOrCorpid = appidOrCorpid; this.aesKey = Base64.decodeBase64(encodingAesKey + "="); } /** * ???(?:http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=4_3) * * @param packageParams ? * @param signKey Key(? Key) * @return ?? */ public static String createSign(Map<String, String> packageParams, String signKey) { SortedMap<String, String> sortedMap = new TreeMap<>(); sortedMap.putAll(packageParams); List<String> keys = new ArrayList<>(packageParams.keySet()); Collections.sort(keys); StringBuffer toSign = new StringBuffer(); for (String key : keys) { String value = packageParams.get(key); if (null != value && !"".equals(value) && !"sign".equals(key) && !"key".equals(key)) { toSign.append(key + "=" + value + "&"); } } toSign.append("key=" + signKey); String sign = DigestUtils.md5Hex(toSign.toString()).toUpperCase(); return sign; } static String extractEncryptPart(String xml) { try { DocumentBuilder db = builderLocal.get(); Document document = db.parse(new InputSource(new StringReader(xml))); Element root = document.getDocumentElement(); return root.getElementsByTagName("Encrypt").item(0).getTextContent(); } catch (Exception e) { throw new RuntimeException(e); } } /** * ???. * <ol> * <li>????AES-CBC</li> * <li>???</li> * <li>????xml?</li> * </ol> * * @param plainText ???xml? * @return ???msg_signature, timestamp, nonce, encryptxml? */ public String encrypt(String plainText) { // String encryptedXml = encrypt(genRandomStr(), plainText); // ??? String timeStamp = Long.toString(System.currentTimeMillis() / 1000l); String nonce = genRandomStr(); try { String signature = SHA1.gen(token, timeStamp, nonce, encryptedXml); return generateXml(encryptedXml, signature, timeStamp, nonce); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } /** * . * * @param plainText ? * @return ?base64? */ protected String encrypt(String randomStr, String plainText) { ByteGroup byteCollector = new ByteGroup(); byte[] randomStringBytes = randomStr.getBytes(CHARSET); byte[] plainTextBytes = plainText.getBytes(CHARSET); byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder(plainTextBytes.length); byte[] appIdBytes = appidOrCorpid.getBytes(CHARSET); // randomStr + networkBytesOrder + text + appid byteCollector.addBytes(randomStringBytes); byteCollector.addBytes(bytesOfSizeInNetworkOrder); byteCollector.addBytes(plainTextBytes); 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?? return base64.encodeToString(encrypted); } catch (Exception e) { throw new RuntimeException(e); } } /** * ???. * <ol> * <li>??????</li> * <li>????xml?</li> * <li>?</li> * </ol> * * @param msgSignature ??URL?msg_signature * @param timeStamp URL?timestamp * @param nonce ?URL?nonce * @param encryptedXml POST? * @return ? */ public String decrypt(String msgSignature, String timeStamp, String nonce, String encryptedXml) { // ?app corpSecret // ??? String cipherText = extractEncryptPart(encryptedXml); try { // ??? String signature = SHA1.gen(token, timeStamp, nonce, cipherText); if (!signature.equals(msgSignature)) { throw new RuntimeException("???"); } } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } // String result = decrypt(cipherText); return result; } /** * . * * @param cipherText ? * @return */ public String decrypt(String cipherText) { 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(cipherText); // original = cipher.doFinal(encrypted); } catch (Exception e) { throw new RuntimeException(e); } String xmlContent, from_appid; try { // ? byte[] bytes = PKCS7Encoder.decode(original); // 16??,?AppId byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); int xmlLength = bytesNetworkOrder2Number(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 RuntimeException(e); } // appid?? if (!from_appid.equals(appidOrCorpid)) { throw new RuntimeException("AppID?"); } return xmlContent; } /** * ???4?bytes * * @param number */ private byte[] number2BytesInNetworkOrder(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?bytes? * * @param bytesInNetworkOrder */ private int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) { int sourceNumber = 0; for (int i = 0; i < 4; i++) { sourceNumber <<= 8; sourceNumber |= bytesInNetworkOrder[i] & 0xff; } return sourceNumber; } /** * ??16? * * @return */ private String genRandomStr() { String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 16; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } /** * ?xml? * * @param encrypt ?? * @param signature ?? * @param timestamp * @param nonce ? * @return ?xml */ private String generateXml(String encrypt, String signature, String timestamp, String nonce) { return String.format(XML_TEMPLATE, encrypt, signature, timestamp, nonce); } }