微信接入探秘(三)——加密消息的处理

版权声明:本文为博主原创文章,更多博文请访问:http://blog.csdn.net/chaijunkun,未经博主允许不得转载。 https://blog.csdn.net/chaijunkun/article/details/53435972

本文出处:http://blog.csdn.net/chaijunkun/article/details/53435972,转载请注明。由于本人不定期会整理相关博文,会对相应内容作出完善。因此强烈建议在原始出处查看此文

在上一篇博文中,介绍了被动回调接口的抽象数据层次,并实现了一个高效的实体与XML互转的基础功能。细心的开发者在初次修改公众号的基本配置时会发现一个名为“消息加解密方式”的选项。展开后可供选择的值为:“明文模式”(默认)、“兼容模式”和“安全模式(推荐)”。那么这个安全模式又是什么呢?如何适配安全模式呢?带着这些问题,来进入微信接入的第三次探秘之旅。

本专栏代码可在本人的CSDN代码库中下载,项目地址为:https://code.csdn.net/chaijunkun/wechat-common

可恶的流量劫持

在文章的一开始,先给大家看两幅处于正常使用中的手机屏幕截图:

2016年9月北京联通的流量劫持

在屏幕的右下角你会看到都有个绿色的水球显示,这与要显示的画面格格不入,很明显不是设计者为之。点开水球,显示的是当前通讯套餐中剩余的流量详情。

经过多方查证,它来自于北京联通。运营商强制将自己的代码注入到了当前显示的页面当中。也就是说,ISP在没经过用户同意的前提下,拦截了我们访问的内容,经过篡改后再返回给我们。例子中仅仅是流量提醒,有一些小规模的ISP甚至过分到直接私自插入广告。面对运营商级别的不法侵害,只有技术升级,采取一定的加密手段才能防止数据被非法篡改,其中HTTPS就是很好的方式。这也是为什么淘宝、京东等公司在全站实现HTTPS,

中间人监听

从家庭局域网络到Internet再到IDC机房内网,我们的数据链路充满了转发的过程。我们的数据在经过任何一台设备的转发时都有可能被其持久化保存起来。你的办公内网一定安全吗?潜伏在你身边的黑客,通过设置局域网内某个网卡为混杂模式(Promiscuous Mode)可以将局域网中的所有数据都接收一份。可能你并不能发现什么,然而你的私有数据已经被别人窃取。

使用安全模式保护用户隐私

微信的被动回调接口提供了一套基于消息加密的防范措施,来抵御这些我们面临的众多安全威胁。这里请注意,如果你提供的回调地址是https协议的,那么开启明文模式即可,因为整个传输链路层对你来说已经是安全的了。

安全模式的回调数据格式

当启用安全模式之后,被动回调接口接收到的消息就产生了很大的变化,形式如下所示:

<xml>
    <ToUserName><![CDATA[gh_38a2de904e09]]></ToUserName>
    <Encrypt><![CDATA[i7b8ccNA9OWDhau/F26aUWKFJ6Jd0imsDQIFPSdSfAg8mHT7rL0kIWSVpcqf6/dVSoOQOQK4T/CS3w96j4k3qcg89M6xn2RGZBs+9JkrsdRig5yhcia1B59akWb1t9QdutXqnl4edAqtXEh8SIs+N2HkOTTVldtOUHpdwLqRYuC4F6ejUoXui4xKuc3oyODR9edfL+xzZ7JfMJ1KUNF/YBJMj/Ba9y/CLLYmdFYOtCMH7tMUz8h+S0XKkHKN6r0ELLCIZJ9+PPlHZcfSGhwMLUeRF1nMIjXGEKHkI0uMcruh7wD96lMU/RFgJDjAk26xbmUYfa3l+34p+txw4R8iD3Q58S8Yekiy3lUsbk+C6eDeefGs1ck23BQ8xWU3AReWH2dEsY6SYIkb3ANeyJmcoIKZfpc/31njp0KcHAxL1Lk=]]></Encrypt>
</xml>

可以看出,明文部分保留了该消息的公众号原始ID:gh_38a2de904e09,其余部分均为加密文本(这种结构可以使得同一个后台系统,接入多个公众号,再利用每个公众号的设置进行分别解密)。由于加密结构在官方文档(http://qydev.weixin.qq.com/wiki/index.php?title=加解密库下载与返回码)中并没有明确的阐述,这里就给大家详细地说明一下。

Created with Raphaël 2.1.0 文本密文 文本密文 字节密文 字节密文 字节数据 字节数据 16字节随机数 16字节随机数 4字节内容长度标识 4字节内容长度标识 明文数据 明文数据 appId appId Base64解码 AES解密 提取 提取 得到明文长度n 从长度标识后读取n字节,UTF-8编码 记录明文结束点m 从明文结束点m读到末尾,UTF-8编码

如果读者以前开发过网络协议应该很容易理解这个结构,典型的不定长消息。一定会有一个标识来指明不定长区域的长度,读取时以该值为准,在定长数据之后读取若干字节。在结构的开头,有16字节随机数,该部分数据无实际意义,可用于后续返回加密消息时作为随机数的填充数据。

安全模式的签名验证方法

微信的被动回调接口实际上是一个HTTP POST请求,我们都知道HTTP请求分为请求头和请求体。微信巧妙地将加密的XML数据放在了请求体,将验证签名时使用的参数放入了请求头。

POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6&timestamp=1409659813&nonce=1372623149 HTTP/1.1
Host: qy.weixin.qq.com
Content-Length: 613
<xml>
    <ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName>
    <Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt>
</xml>

签名生成的过程

Created with Raphaël 2.1.0 接入配置 接入配置 请求 请求 时间戳 时间戳 随机字符 随机字符 回调加密XML 回调加密XML Token Token 参数收集容器 参数收集容器 拼接字符串 拼接字符串 签名 签名 从头部获取 从头部获取 从请求体获取 读取 装入 装入 装入 装入 按字典序排序 取SHA1摘要

通过上述计算,现在得出了当前加密消息的签名calculated_sign,然后从请求头中获取参数msg_signature,如果calculated_sign和msg_signature相同,则说明消息没有被篡改过。这种签名策略利用的数学难题有:
1. SHA1摘要算法对全部数据进行了计算,有任何改动最终的摘要都会发生变化;
2. Token是事先设置好的,在传输过程中并不包含,无从破解;
3. 时间戳和随机字符的引入,使得即便明文相同,每一次加密密文都有不同,给破解AES加密增加难度

签名验证成功之后就可以放心地使用解密后的明文了。明文的格式和之前文章中介绍的格式一致,因此解密后的业务处理逻辑可以复用。

作为安全模式的应答,返回的XML格式也应该是加密的,整个加密过程与解密过程方向相反,这里就不再赘述了。

2017年5月26日补充:微信的加密消息核心使用AES方式加密。在生产环境中,由于解密消息超过了JDK默认的安全限制值,解密时会抛出异常java.security.InvalidKeyException:illegal Key Size。解决该问题可以在官方网站下载JCE无限制权限策略文件:
a.如果使用的是JDK7:下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
b.如果使用的是JDK8:下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt。
如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件,如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件。

猜你喜欢

转载自blog.csdn.net/chaijunkun/article/details/53435972