在最近的开发需求中,有一个需求,就是需要把微信退款通知记录在数据库中,原本以为是一个简单的需求,但是微信文档的坑,是你不能理解的。
在微信退款通知返回的字段中有一个加密信息字段req_info。这个加密字段需要三个解密步骤才能最终获取到信息。由此可见,这个字段是多么的重要。以下是微信官方文档给出的解密步骤:
解密步骤如下:
(1)对加密串A做base64解码,得到加密串B
(2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
(3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
第一步,第二步都好理解,但是第三步说的真是太简单了。被坑了一天,现在把代码留下。
1.通过微信退款通知先获取到req_info这个字段
public static Map<String, String> xmlToMap(HttpServletRequest request) throws Exception {
Map<String, String> map = new HashMap<String, String>();
SAXReader reader = new SAXReader();
InputStream ins = request.getInputStream();
Document doc = reader.read(ins);
Element root = doc.getRootElement();
List<Element> list = root.elements();
for (Element e : list) {
map.put(e.getName(), e.getText());
}
ins.close();
return map;
}
这里使用DOM4j 首先把返回来的xml转换成map。从中获取req_info。
2.就是对req_info这个字段信息进行解密。
1. 对加密信息进行base64位解码。
byte[] b = org.bouncycastle.util.encoders.Base64.decode(req_info);
2.对商户API秘钥做MD5加密,得到小写的32位key
String key* = getMD5("your API key").toLowerCase().getBytes();
3.用刚才得到的key对加密串B做AES-256-ECB解密(PKCS7Padding)
public static String decryptData(byte[] b) throws Exception {
Cipher cipher = null;
cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
SecretKeySpec key = new SecretKeySpec(key*.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(b));
}
解密过后,就会返还xml,需要对xml进一步解析。
在整个解密过程中需要注意以下几点:
1. java 目前只支持PKCS5Padding,所以需要额外添加依赖。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-ext-jdk16</artifactId>
<version>1.45</version>
</dependency>
2.如果报这个错 Illegal key size or default parameters 就需要替换你JAVA_HOME/jre/lib/security 下面的两个jar包 local_policy.jar和US_export_policy.jar。
3.在解密类中需要添加一个静态代码块来提供支持,静态代码块如下:
static {
try {
Security.addProvider(new BouncyCastleProvider());
} catch (Exception e) {
e.printStackTrace();
}
}
以下 附上完整代码:
public class CipherTextUtil {
private static final String serectKey = "your API Serect key";
/**
* 密钥算法
*/
private static final String ALGORITHM = "AES";
/**
* 加解密算法/工作模式/填充方式
*/
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
/**
* 生成key
*/
private static SecretKeySpec key = new SecretKeySpec(MD5Util.MD5Encode(serectKey).toLowerCase().getBytes(), ALGORITHM);
static {
try {
Security.addProvider(new BouncyCastleProvider());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* AES解密
*/
public static String decryptData(byte[] b) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(b));
}
public static void main(String[] args) throws Exception {
String A = "cms0dmeMS39cUWRUz19yJiPifeLRXoZjXhOnIhQlrz4SxDa2kmPVxHkMv2BeE+H+xr8euGyAO9sBNmS9ffugL7Xj47b9K6bwI8BLrP6/SwL3nTcrm44oimbJ4axAQLoaNuheqWYFS7pRBXAbByjjsBLzdMh+thCJjhFvyCK61fRMaUlkn6eZsc7fs609KgELGqSBdDlNsbXpTurgqdLdW07jPlsJZyXshg1gJ3b+n2wMC/pEZwPA/w7E0bAt0xzTIsYKmtLEwJlWg6Vf1KiocDfU0oVnhd5JPozxx4Cve67kUuGqDpdoStTBMXywHoAytklE610DvVKT59gkK9MqFU97SXV714wko6ZRKQAW0Utpx9DUko4jV3Hdwspr1JYUAg/GnINom25YbWQqKp28VKkJveXGeif5GEeUxeAmr4mYGvf9vD+bvBP2sH06koiOTJVVuIhvWmAdmFnNdk2hJkJdKvEtxxaNP7/eha5jDKNgjvCGV/hJNasl8U3qZX1qR0RtvcgoORt3nNvUOjQESNeS+tXlZtcpdZqDR4g7kQl5grAs+BOF6rk0qJ0PagxWF+arDSReLYBN5ihBkvai56s5Cv9iXFpVNkMwTBqYC4OpqzIN1EecA5fpi9pk7nVB3v5HLwp3oBYhbNiPNXkGTkuw634wBQtDLmeG3mXAYTX4xWdsYrkg4RY/rqFKsgeeBzGs7a7+PgbcE8B/t4/jVX3qVwbBkacqhG1l/W0zfZA8vHOJzVJhWEjXzn1iccQLiA6OASfnvRYoPnBoCX1qIjo0e55oCvRDau7hVTSvBx0hcpIjXmlcMcpJgiRXrPZFXgWh9myRkYtkCRleX49NELBOnK+sRHuJlfnpYYs9d1TurWzhXuu6wMow/wGQtyGC3XtQv0QTTg1KKDPRiqYq0QXMJ28+jL216Eg8qGrxmzXGYJ9Nf3oLh2TOZZvdhBbj+EM8T9+c/h3bAEe2L1wYtEJrqAMiSwZ6qW4dElQHBwL7sWha63gIB37vJ0SbyjWkqWWLUwwOMkDgvJ9r5I58O4OgzBtinC9k9yl90wO/dm0yuDTwF9agYlsPFP8XXClVdnvGQd+HUPk9YKB+WkyUww==";
byte[] b = Base64.decode(A);
String B = AESUtil.decryptData(b);
System.out.println(B);
}
}
完结,撒花撒花。。。。。