加解密方案说明
发布时间:2026-10-09 | 浏览:1
附注:ReceiveId 含义
企业微信在推送消息给企业时,会对消息内容做AES加密,以XML格式POST到企业应用的URL上。 企业在被动响应时,也需要对数据加密,以XML格式返回给企业微信。 本章节即是对加解密方法的说明。 阅读本章节前,需要了解以下术语:
msg_signature: 消息签名,用于验证请求是否来自企业微信(防止攻击者伪造)。
EncodingAESKey:用于消息体的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,是AESKey的Base64编码。解码后即为32字节长的AESKey AESKey=Base64_Decode(EncodingAESKey + “=”)
AESKey:AES算法的密钥,长度为32字节。 AES采用CBC模式,数据采用PKCS#7填充至32字节的倍数;IV初始向量大小为16字节,取AESKey前16字节,详见: https://tools.ietf.org/html/rfc2315
msg:为消息体明文,格式为XML
msg_encrypt:明文消息msg加密处理后的Base64编码。
鉴于加解密算法相对复杂,企业微信提供了算法库。 目前已有c++/python/php/java/golang/c#等语言版本。均提供了解密、加密、验证URL三个接口,企业可根据自身需要下载, 下载地址 。
以c++为例,使用示例见下载的文件夹中的Sample.cpp, 此处做简单说明。
要求传参数sToken,sEncodingAESKey,sReceiveId。 sToken,sEncodingAESKey即 设置接收消息的参数 章节所述配置的Token、EncodingAESKey。 特别注意, sReceiveId 在不同场景下有不同含义,见 附注 。
解密数据包,得到明文消息结构体
目前官方已提供了php、python、c++等版本的加解密库,如果开发者需要进行别的语言的开发,需要自行根据加解密原理实现算法。
为了让企业确认调用来自企业微信,企业微信在回调给接收消息url时会带上消息签名,以参数msg_signature标识,企业需要验证此参数的正确性后再解密。 验证步骤如下:
计算签名 dev_msg_signature=sha1(sort(token、timestamp、nonce、msg_encrypt))。 sort的含义是将 参数值 按照字母字典排序,然后 从小到大 拼接成一个字符串 sha1处理结果要编码为可见字符,编码的方式是把每字节散列值打印为%02x(即16进制,C printf语法)格式,全部小写
比较dev_msg_signature和msg_signature是否相等,相等则表示验证通过
在被动响应消息时,企业同样需要用如上方法生成签名并传给企业微信
拼接明文字符串 rand_msg = random(16B) + msg_len(4B) + msg + receiveid 明文字符串由16个字节的随机字符串、4个字节的msg长度、明文msg和receiveid拼接组成。其中msg_len为msg的字节数,网络字节序;sReceiveId 在不同场景下有不同含义,见 附注
对明文字符串加密并Base64编码 msg_encrypt = Base64_Encode(AES_Encrypt(rand_msg)) 将明文字符串AESKey加密后,再进行Base64编码,即获得密文msg_encrypt。
对密文BASE64解码 aes_msg=Base64_Decode(msg_encrypt)
使用AESKey做AES-256-CBC解密 rand_msg=AES_Decrypt(aes_msg)
去掉rand_msg头部的16个随机字节和4个字节的msg_len,截取msg_len长度的部分即为msg,剩下的为尾部的receiveid
验证解密后的receiveid、msg_len。注意,receiveid在不同场景含义不同。
假设在服务商管理端为某个套件有如下配置参数:
收到来自企业微信的回调为: xml请求示例:
token、timestamp、nonce、msg_encrypt 这四个参数按照字典序排序 "1372623149" "1409659813" "QDG6eK" "RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q=="
拼接为一个字符串 sort_str = "13726231491409659813QDG6eKRypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q=="
对该字符串进行sha1计算得到签名 signature = sha1 ( sort_str ) = "477715d11cdb4164915debcba66cb864d751f3e6"
对比从URL得到的签名,发现两者一致,签名通过,说明没被篡改,是安全的
对密文base64解码 aes_msg = base64_decode ( msg_encrypt )
使用AESKey做AES解密( 注意,不是EncodingAESKey ) rand_msg = aes_decrypt ( aes_msg , AESKey )
去掉rand_msg头部的16个随机字节和4个字节的msg_len,截取msg_len长度的部分即为msg,剩下的为尾部的receiveid 下面为类似python的伪代码 content = rand_msg [ 16 : ] # 去掉前16随机字节 msg_len = str_to_uint ( content [ 0 : 4 ] ) # 取出4字节的msg_len msg = content [ 4 : msg_len + 4 ] # 截取msg_len 长度的msg receiveid = content [ msg_len + 4 : ] = "wx5823bf96d3bd56c7" # 剩余字节为receiveid 若回调协议格式为xml,对于回调xml解密后得到明文为: < xml > < ToUserName > <![CDATA[wx5823bf96d3bd56c7]]> </ ToUserName > < FromUserName > <![CDATA[mycreate]]> </ FromUserName > < CreateTime > 1409659813 </ CreateTime > < MsgType > <![CDATA[text]]> </ MsgType > < Content > <![CDATA[hello]]> </ Content > < MsgId > 4561255354251345929 </ MsgId > < AgentID > 218 </ AgentID > </ xml >
根据明文中的MsgType可知,此为应用消息回调,因此receiveid应该为corpid,对比receiveid与corpid是否一致。
附注:ReceiveId 含义
加解密库里,ReceiveId 在各个场景的含义不同:
企业应用的回调,表示corpid
第三方事件的回调,表示suiteid
个人主体的第三方应用 的回调,ReceiveId是一个空字符串