内测中
加密版接口内测功能
目前这个功能处于内测阶段,如果对安全性要求比较高,则可以联系客服开启这个加密版接口的功能
在菜单栏【智能对话】-【api接入】中 开启升级版API
(该操作无法回退,开启后无法使用老版本的api发消息),开启后会自动生成AppKey
、AppSecret
、EncodingAESKey
。
Appkey
标识当前组的唯一id,用于生成accessToken
的参数之一,不可重置。
AppSecret
AppSecret
是小组用于保障数据安全的“钥匙”,每一个小组都有一个独立的访问密钥,为了保证数据的安全,AppSecret
务必不能泄漏。用于生成access_token的参数之一,可以重置,重置后会生成新的AppSecret
,并且依赖之前AppSecrt对应生成的accessToken
也会失效。
AccessToken
用来替代老版token。生成的额accessToken
默认有效时间是7200s,调用者可以传入时间来指定accessToken
的有效时间。
EncodingAESKey
EncodingAESKey
用于回调消息内容加密,由英文或数字组成且长度为43位的自定义字符串。EncodingAESKey
可以重置,暂无通过接口获取EncodingAESKey
,一经重置,客户需要通过手动复制粘贴到代码中使用。
相关api
生成accessToken
POST https://ex-api.botorange.com/getAccessToken
Body 请求参数
{
"token": "62a2e8169dddfbdd9aea5c85",
"appKey": "62a2a0c3989d7260ff537c36",
"appSecret": "nQm3X59gmyu58zvHICAFp8oIymDS5wLKPVnL3xQhYzJHEizpdX",
}
请求参数:
名称 | 位置 | 类型 | 必选 | 说明 |
---|---|---|---|---|
token | body | string | 是 | 用来鉴权你是否有权限生成accessToken |
appKey | body | string | 是 | 页面中生成的appKey |
appSecret | body | string | 是 | 页面中生成的appSecret |
返回格式:
{
"code": 0,
"message": "",
"data": {
"accessToken":"4tKm1ncg4Pxe9wN4M7dMhQqPITLYfyVShc8BHNNsdLfK7WKFy2Kufq6Sfqh5vzhB",
"expiresIn":7200
}
}
返回参数:
- accessToken:根据
appKey
和appSecre
t生成的token。 - expiresIn:
accessToken
的有效期。单位:秒
注意事项:
开发者需要缓存
accessToken
,当accessToken
失效或过期时,需要重新获取。accessToken
的有效期通过返回的expiresIn来传达,正常情况下为7200秒(2小时),有效期超过1200秒(20分钟)重复获取返回相同结果,有效期剩余不超过1200秒(20分钟)获取会返回新的accessToken
。页面上重置
appSecret
会使之前生成的accessToken立即失效。
关于EncodingAESKey
说明
一旦升级到新版本的api,所有通过回调地址回调的消息都会使用EncodingAESKey
作为密钥来加密。
句子在推送消息给企业时,如果企业已经升级了api,那么句子会对消息内容做AES加密。这里的加密的消息内容和老版本的消息内容格式保持一致。参考:小组控制台接口(legacy)
一些参数说明
msgSignature: 消息签名,用于验证请求是否来自企业微信(防止攻击者伪造)
EncodingAESKey:用于消息体的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,是AESKey的
Base64
编码。解码后即为32字节长的AESKey
const AESKey = Buffer.from(`${encodingAESKey}=`, 'base64');
AESKey:AES算法的密钥,长度为32字节。 AES采用CBC模式,数据采用
PKCS#7
填充至32字节的倍数;IV初始向量大小为16字节,取AESKey前16字节,详见:http://tools.ietf.org/html/rfc2315msg:为消息体明文,格式为JSON
msgEncrypt:明文消息msg加密处理后的
Base64
编码。
回调函数返回值
加密的回调数据返回值如下
{
msgEncrypt: 'YOh0ibimQ4YGo9xNURbGP9kKe0ZBfp4ewNYE5NNM1n6vcuSLEQhZ+6aW7W2pghGT3HTWUbsngEgHKFIzCJShEeEtWMnedSM8rcnLn3b7KiptLxtHYU45WfdqFZ6/Al2kk0Z347sRkWmlwyB5V+8KzmgY/c97QpeJjeAxDUCw752A3HIIzPrIE3IoGBf+PuaA+U6/7mRltcWd8YUVwnQXp095MUZhNI5etpBpQcSJd/lOtv7NJISZ3ei8ez1TLQXCeDfA25785fY5TBwK/KJN7p30otMseTatxeaCnai4xM76P3xk6UdUZl5V5R1V3POrUeA2ZZbXlVRBGJ+fx0tfIPNBkwNVnFGUGCrzRgImUquYmdXvElfls71wEYarHvC/jwjM9YstFkRCJisDN7FIhN9sV/hoLux72o4qj96mlxNogcZ+0e169cNLbrBKbVTyiVZbuuk1pSHudaOIWQxDMZrL6EdwZ04eKbJztAkmUcPLPwvidzsssBLeZrfa9bHIqS4zjGWBSTxY5UclAkKGSThWExvXwpHuwOOkyUTjA3RUZrr4/S+NGJL20nuA6P/EAlaLyAv5XP8rPS/9M66/wy+bVaAuRvR+JIfpdO2zwe1IV4EN4NfD/PydGptSpR6vgBUyBNHC7LixprTwbbf8TvleDZYLnBF/e/L2VXplGuc=',
msgSignature: 'fe9a0e585838cc0a5cfaba60666c035244d64591',
timestamp: 1655437691133,
nonce: '1548674977'
}
以上消息解密后为
{
message: '{"data":{"messageId":"1224501","chatId":"62a2a13e634db54d8183b2fd","avatar":"https://wx.qlogo.cn/mmhead/Q3auHgzwzM4o5pDPk656opaSJrUGlicw0xicWV9GKhKyfyo7RwSdXJiaw/0","roomTopic":"","roomId":"","contactName":"福利官是你2","contactId":"7881302521067024","payload":{"text":"test"},"type":7,"timestamp":1655437690022,"token":"62a2e8169dddfbdd9aea5c85","contactType":1,"coworker":false,"botId":"62a2a0c747939dae52ec3f32","botWxid":"1688857603302323","botWeixin":"lihuiming"}}',
id: '',
random: <Buffer 75 3f 6b f9 ed bc b2 a0 4d 7a 91 dd 90 b7 9c e7>
}
然后将message字段转成json为,这个数据格式就是和小组控制台接口(legacy)的格式保持一致的
{
data: {
messageId: '1224501',
chatId: '62a2a13e634db54d8183b2fd',
avatar: 'https://wx.qlogo.cn/mmhead/Q3auHgzwzM4o5pDPk656opaSJrUGlicw0xicWV9GKhKyfyo7RwSdXJiaw/0',
roomTopic: '',
roomId: '',
contactName: '福利官是你2',
contactId: '7881302521067024',
payload: { text: 'test' },
type: 7,
timestamp: 1655437690022,
token: '62a2e8169dddfbdd9aea5c85',
contactType: 1,
coworker: false,
botId: '62a2a0c747939dae52ec3f32',
botWxid: '1688857603302323',
botWeixin: 'lihuiming'
}
}
本部分的内容,与企业微信的加解密原理相同。 参考:https://developer.work.weixin.qq.com/devtool/introduce?id=36388
验证签名
/**
* 获取签名
*
* @param token 页面中AppSecret
* @param timestamp 时间戳
* @param nonce 随机串
* @param encrypt 密文msgEncrypt
*/
export declare function getSignature(
token: string,
timestamp: string | number,
nonce: string | number,
encrypt: string,
): string;
计算签名
devMsgSignature=sha1(sort(AppSecret、timestamp、nonce、msgEncrypt))。
sort
的含义是将参数值按照字母字典排序,然后从小到大拼接成一个字符串sha1
处理结果要编码为可见字符,编码的方式是把每字节散列值打印为%02x(即16进制,C printf语法)格式,全部小写比较
devMsgSignature
和msgSignature
是否相等,相等则表示验证通过
解密函数
/**
* 解密
*
* @param encodingAESKey EncodingAESKey
* @param encrypt 密文msgEncrypt
*/
export declare function decrypt(
encodingAESKey: string,
encrypt: string,
): {
message: string;
id: string;
random: Buffer;
};
- 对密文base64解码
aesMsg = Buffer.from(msgEncrypt, 'base64');
- 使用AESKey做AES解密(注意,不是EncodingAESKey)
const AESKey = Buffer.from(`${encodingAESKey}=`, 'base64');
randMsg = aesDecrypt(aesMsg, AESKey)
- 去掉randMsg头部的16个随机字节和4个字节的msgLen,截取msgLen长度的部分即为msg,剩下的为尾部的id(暂无实际意义)
const content = randMsg.slice(16) # 去掉前16随机字节
const msgLen = content.readUInt32BE(0); # 取出4字节的msgLen
const msg = content.slice(4, msgLen+4) # 截取msgLen 长度的msg
const id = content.slice(msgLen+4) = "" # 剩余字节为id (暂无实际意义)
// msg 转为json
JSON.parse(msg.toString())
// 明文如下
{
data: {
messageId: '1224501',
chatId: '62a2a13e634db54d8183b2fd',
avatar: 'https://wx.qlogo.cn/mmhead/Q3auHgzwzM4o5pDPk656opaSJrUGlicw0xicWV9GKhKyfyo7RwSdXJiaw/0',
roomTopic: '',
roomId: '',
contactName: '福利官是你2',
contactId: '7881302521067024',
payload: { text: 'test' },
type: 7,
timestamp: 1655437690022,
token: '62a2e8169dddfbdd9aea5c85',
contactType: 1,
coworker: false,
botId: '62a2a0c747939dae52ec3f32',
botWxid: '1688857603302323',
botWeixin: 'lihuiming'
}
}
加密函数
/**
* 加密
*
* @param encodingAESKey EncodingAESKey
* @param message 消息文本
* @param id 暂时没有使用,传的空字符串
* @param random 随机串
*/
export declare function encrypt(
encodingAESKey: string,
message: string,
id: string,
random?: Buffer,
): string;
- 拼接明文字符串
randMsg = random(16B) + msgLen(4B) + msg + receiveid
明文字符串由16个字节的随机字符串、4个字节的msg长度、明文msg和receiveid拼接组成。其中msgLen为msg的字节数,网络字节序;receiveid暂无实际意义
- 对明文字符串加密并Base64编码
msg_encrypt = Base64_Encode(AES_Encrypt(rand_msg))
将明文字符串AESKey加密后,再进行Base64编码,即获得密文msg_encrypt。
回调案例
/message
参数如下:
appKey:62ac92c12c4b8587132ab8d9
appSecret:YxHi27WiYe5k0dYiVmRFYdolJp9RPGuNmQ5JgaqrMfLKUoB5XV
token:62ac92c52c4b8587132ab8da
encodingAESKey:25fHA3xB67lRgS2MBwW7w0km1K30ye9PzSnfMGOJslp
原文如下,改格式和原来不加密的消息格式保持一致小组控制台接口(legacy)
{
data: {
messageId: '1227832',
chatId: '62ac932b191e766df2f378d7',
avatar: 'https://wx.qlogo.cn/mmhead/Q3auHgzwzM4o5pDPk656opaSJrUGlicw0xicWV9GKhKyfyo7RwSdXJiaw/0',
roomTopic: '',
roomId: '',
contactName: '福利官是你2',
contactId: '7881302521067024',
payload: { text: '句子科技' },
type: 7,
timestamp: 1655692898706,
token: '62ac92c52c4b8587132ab8da',
contactType: 1,
coworker: false,
botId: '62ac92d05a1297d122822b96',
botWxid: '1688857603302323',
botWeixin: 'lihuiming'
}
}
加密后的格式如下:
{
msgEncrypt: 'dr4zEQpV3M9lfu2Bx0rUKPGyzxUs9vP+bWd0nXAP24bgqzgJGIQ0TEaZxeVF4oEwXAR1lI5TiknrIhu0RuDF5+nDjVuX/wHzaLVnNbpTbNZu1StBkMqnvINWii8DGwbRkOc4lcz8ca5RTw0yTDR5ZkKEggr68wVPlBoiifptepgYhZwN5R9jW6pGyg1vtUDmrGK4trtlmipH2ZAJjg/wwvq/mdAcMdIKnuCfWzO+HHNbvEShXrROHJ4PWYI6pibKKDDxFd2vz8qbIoFxWeX7ELsqr4iKOEYG9WTB0ukfQn9k/PcWn4YrKWsutnYhQseXhyWy7GAv22kyCIyXR5vP13lGvkyUjVD8HhSw1ExiopydP4AuFFeWX5Yb4zN2uczL6r4jTXIYcnm7evHaPMZN/wLCQiiezLizdoiEeuvaZbqVsgfKWZi6wfS8sOhvi2mDxGRF9dItSOkjq7QIRqJ2fBjv/xfCyj1Ev3T1n9EalbLbGQFVvjza705vC8LlslAgTIvclRiq2ynT62WeLdKWco+bb4E+NA3M6v1BpT136+2Ajz0KCMaEwAF+y+ssg4TNyjC92yVhRAiSLWCFnw/DQcQ7D9BBJ17tOWaneqLZ3mjOdcKZ1f55H74w98ksOJXXqzsp60JJK7EvoR4i6HqA9luEazhFdrvKiyU3VifYtjc=',
msgSignature: 'e236ba4180eb9c242cbe6ecdeabc5dc52ed17f6c',
timestamp: 1655692899577,
nonce: '0678228500'
}
校验signature:
const devMsgSignature = getSignature(TOKEN, timestamp, nonce, msgEncrypt);
devMsgSignature: e236ba4180eb9c242cbe6ecdeabc5dc52ed17f6c
解密后的格式如下:
{
message: '{"data":{"messageId":"1227832","chatId":"62ac932b191e766df2f378d7","avatar":"https://wx.qlogo.cn/mmhead/Q3auHgzwzM4o5pDPk656opaSJrUGlicw0xicWV9GKhKyfyo7RwSdXJiaw/0","roomTopic":"","roomId":"","contactName":"福利官是你2","contactId":"7881302521067024","payload":{"text":"句子科技"},"type":7,"timestamp":1655692898706,"token":"62ac92c52c4b8587132ab8da","contactType":1,"coworker":false,"botId":"62ac92d05a1297d122822b96","botWxid":"1688857603302323","botWeixin":"lihuiming"}}',
id: '',
random: <Buffer 81 a6 c4 9d 5b 0c 33 22 a7 b5 d3 54 23 f1 78 39>
}
id暂时为空,无实际意义。message
字段就是原消息明文。
还原明文,对message
进行JSON.parse
{
data: {
messageId: '1227832',
chatId: '62ac932b191e766df2f378d7',
avatar: 'https://wx.qlogo.cn/mmhead/Q3auHgzwzM4o5pDPk656opaSJrUGlicw0xicWV9GKhKyfyo7RwSdXJiaw/0',
roomTopic: '',
roomId: '',
contactName: '福利官是你2',
contactId: '7881302521067024',
payload: { text: '句子科技' },
type: 7,
timestamp: 1655692898706,
token: '62ac92c52c4b8587132ab8da',
contactType: 1,
coworker: false,
botId: '62ac92d05a1297d122822b96',
botWxid: '1688857603302323',
botWeixin: 'lihuiming'
}
}