消息体加密
随着微信服务开发在越来越多的领域应用,应用的安全性逐渐被重视起来。本文主要阐述如何为微信的消息加密的原理与Java版本的实现。
原理
做过微信开发的朋友们都知道,微信使用xml格式的消息结构。这里着重说一下最核心的收发消息。
在明文模式中,POST请求有signature
, timestamp
, nonce
三个参数。它的Body为一个XML:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </xml>
而在加密模式中,增加了两个参数:encrypt_type
和 msg_signature
。它的Body 也变更为:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <Encrypt><![CDATA[encryptData]]></Encrypt> </xml>
其中encryptData
是加密后的信息,通过我们在微信开发平台配置的EncodingAESKey
, Token
, 以及自动生成的AppID,通过加密算法,可以验证信息的真实性,并且将真实的信息,解析成如明文的格式的XML。整条信息是加密的,因此不用担心被伪造,否则,会有风险。
PS:会有怎样的风险?如果是Token被人猜出来进而伪造内容,加密模式中的EncodingAESKey不会么?还是担心消息类型会泄露?没太想明白,希望路过的大神指点
不管怎么说,加密看起来更安全些,我现在做一个金融类的项目,有备无患,就使用了加密的功能。
代码实现
微信提供了一个各种语言的解析,我在上面稍微封装了一下:
public class SignUtil { /** * 与开发模式接口配置信息中的Token保持一致 */ private static String token = "yourToken"; /** * 微信生成的 ASEKey */ private static String encodingAesKey ="yourKey"; /** * 应用的AppId */ private static String appId="yourId"; /** * 解密微信发过来的密文 * * @return 加密后的内容 */ public static String decryptMsg(String msgSignature,String timeStamp,String nonce,String encrypt_msg) { WXBizMsgCrypt pc; String result =""; try { pc = new WXBizMsgCrypt(token, encodingAesKey, appId); result = pc.decryptMsg(msgSignature, timeStamp, nonce, encrypt_msg); } catch (AesException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } /** * 加密给微信的消息内容 * @param replayMsg * @param timeStamp * @param nonce * @return */ public static String ecryptMsg(String replayMsg,String timeStamp, String nonce) { WXBizMsgCrypt pc; String result =""; try { pc = new WXBizMsgCrypt(token, encodingAesKey, appId); result = pc.encryptMsg(replayMsg, timeStamp, nonce); } catch (AesException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; }
最后,在对应POST的处理方法中调用:
//request 为请求的 HttpServletRequest 参数 String encrypt_type =request.getParameter("encrypt_type"); if (encrypt_type == null || encrypt_type.equals("raw")) { //不用加密 // 微信加密签名 String signature = request.getParameter("signature"); // 时间戳 String timestamp = request.getParameter("timestamp"); // 随机数 String nonce = request.getParameter("nonce"); if(SignUtil.checkSignature(signature, timestamp, nonce)) { //业务处理方法,返回为XML格式的结果信息 此处需要转换一下编码,否则中文乱码,不知为何... String respMessage = new String(wxService.processRequest(request.getInputStream(),session).getBytes("UTF-8"),"ISO8859_1"); logger.info("message:"+respMessage); return respMessage; } else { return "check Error"; } } else {//需走加解密流程 // 微信加密签名 String msgSignature = request.getParameter("msg_signature"); // 时间戳 String timeStamp = request.getParameter("timestamp"); // 随机数 String nonce = request.getParameter("nonce"); //密文 String encryptMsg = readLine(request.getInputStream()); String data = SignUtil.decryptMsg(msgSignature, timeStamp, nonce, encryptMsg); logger.info("parse Data is:"+data); if(data.length() == 0) { return "CheckError"; } else { InputStream istream = new ByteArrayInputStream(data.getBytes()); //业务处理方法,返回为XML格式的结果信息 String respMessage = wxService.processRequest(istream,session); logger.info("message:"+respMessage); String enRespMsg = SignUtil.ecryptMsg(respMessage, timeStamp, nonce); return enRespMsg; } }
使用上面的方法,可以同时处理明文模式与加密模式,从真正意义上做到闲的蛋疼
如果你觉得这篇文章对你有帮助,可以顺手点个顶,不但不会喜当爹,还能让更多人能看到它...