目录
企业微信在推送消息给企业时,会对消息内容做AES加密,以XML格式POST到企业应用的URL上。
企业在被动响应时,也需要对数据加密,以XML格式返回给企业微信。
本章节即是对加解密方法的说明。
阅读本章节前,需要了解以下术语:
AESKey=Base64_Decode(EncodingAESKey + “=”)
鉴于加解密算法相对复杂,企业微信提供了算法库。
目前已有c++/python/php/java/golang/c#等语言版本。均提供了解密、加密、验证URL三个接口,企业可根据自身需要下载,下载地址。
使用现有库,用户不必细究加解密原理。对于找不到相应语言库的用户,请阅读后文原理详解自行实现。欢迎大家分享~
以c++为例,使用示例见下载的文件夹中的Sample.cpp, 此处做简单说明。
回调xml示例:
WXBizMsgCrypt wxcpt(sToken,sEncodingAESKey,sReceiveId);
要求传参数sToken,sEncodingAESKey,sReceiveId。
sToken,sEncodingAESKey即设置接收消息的参数章节所述配置的Token、EncodingAESKey。
特别注意, sReceiveId 在不同场景下有不同含义,见附注。
本函数实现:
int VerifyURL(const string &sMsgSignature, const string &sTimeStamp, const string &sNonce, const string &sEchoStr, string &sReplyEchoStr);
参数 | 必须 | 说明 |
---|---|---|
sMsgSignature | 是 | 从接收消息的URL中获取的msg_signature参数 |
sTimeStamp | 是 | 从接收消息的URL中获取的timestamp参数 |
sNonce | 是 | 从接收消息的URL中获取的nonce参数 |
sEchoStr | 是 | 从接收消息的URL中获取的echostr参数。注意,此参数必须是urldecode后的值 |
sReplyEchoStr | 是 | 解密后的明文消息内容,用于回包。注意,必须原样返回,不要做加引号或其它处理 |
本函数实现:
int DecryptMsg(const string &sMsgSignature, const string &sTimeStamp, const string &sNonce, const string &sPostData, string &sMsg);
参数 | 必须 | 说明 |
---|---|---|
sMsgSignature | 是 | 从接收消息的URL中获取的msg_signature参数 |
sTimeStamp | 是 | 从接收消息的URL中获取的timestamp参数 |
sNonce | 是 | 从接收消息的URL中获取的nonce参数 |
sPostData | 是 | 从接收消息的URL中获取的整个post数据 |
sMsg | 是 | 用于返回解密后的msg,以xml组织,参见普通消息格式和事件消息格式 |
本函数实现:
int EncryptMsg(const string &sReplyMsg, const string &sTimeStamp, const string &sNonce, string &sEncryptMsg);
参数 | 必须 | 说明 |
---|---|---|
sReplyMsg | 是 | 返回的消息体原文 |
sTimeStamp | 是 | 时间戳,调用方生成 |
sNonce | 是 | 随机数,调用方生成 |
sEncryptMsg | 是 | 用于返回的密文,以xml组织,参见被动回复消息格式 |
目前官方已提供了php、python、c++等版本的加解密库,如果开发者需要进行别的语言的开发,需要自行根据加解密原理实现算法。
为了让企业确认调用来自企业微信,企业微信在回调给接收消息url时会带上消息签名,以参数msg_signature标识,企业需要验证此参数的正确性后再解密。
验证步骤如下:
dev_msg_signature=sha1(sort(token、timestamp、nonce、msg_encrypt))。
rand_msg = random(16B) + msg_len(4B) + msg + receiveid
msg_encrypt = Base64_Encode(AES_Encrypt(rand_msg))
aes_msg=Base64_Decode(msg_encrypt)
rand_msg=AES_Decrypt(aes_msg)
假设在服务商管理端为某个套件有如下配置参数:
corpId = "wx5823bf96d3bd56c7"
token = "QDG6eK"
encodingAesKey = "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C"
收到来自企业微信的回调为:
xml请求示例:
POST /cgi-bin/wxpush?msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1
Host: qy.weixin.qq.com
Content-Length: 603
<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>
<AgentID><![CDATA[218]]></AgentID>
</xml>
第一步:准备相关参数
AESKey = Base64_Decode(EncodingAESKey + "=")
signature = "477715d11cdb4164915debcba66cb864d751f3e6";
timestamps = "1409659813";
nonce = "1372623149";
msg_encrypt = "RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==";
第二步:校验签名
"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=="
signature = sha1(sort_str) = "477715d11cdb4164915debcba66cb864d751f3e6"
第三步: 解密消息
aes_msg = base64_decode(msg_encrypt)
rand_msg = aes_decrypt(aes_msg, AESKey)
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>
<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>
加解密库里,ReceiveId 在各个场景的含义不同: