加密版接口 内测中

内测功能

目前这个功能处于内测阶段,如果对安全性要求比较高,则可以联系客服开启这个加密版接口的功能

在菜单栏【智能对话】-【api接入】中 开启升级版API(该操作无法回退,开启后无法使用老版本的api发消息),开启后会自动生成AppKeyAppSecretEncodingAESKey

Appkey

标识当前组的唯一id,用于生成accessToken的参数之一,不可重置。

AppSecret

AppSecret是小组用于保障数据安全的“钥匙”,每一个小组都有一个独立的访问密钥,为了保证数据的安全,AppSecret务必不能泄漏。用于生成access_token的参数之一,可以重置,重置后会生成新的AppSecret,并且依赖之前AppSecrt对应生成的accessToken也会失效。

AccessToken

用来替代老版tokenopen in new window。生成的额accessToken默认有效时间是7200s,调用者可以传入时间来指定accessToken的有效时间。

EncodingAESKey

EncodingAESKey用于回调消息内容加密,由英文或数字组成且长度为43位的自定义字符串。EncodingAESKey可以重置,暂无通过接口获取EncodingAESKey,一经重置,客户需要通过手动复制粘贴到代码中使用。

相关api

生成accessToken

POST https://ex-api.botorange.com/getAccessToken

Body 请求参数

{
    "token": "62a2e8169dddfbdd9aea5c85",
    "appKey": "62a2a0c3989d7260ff537c36",
    "appSecret": "nQm3X59gmyu58zvHICAFp8oIymDS5wLKPVnL3xQhYzJHEizpdX",
}

请求参数:

名称位置类型必选说明
tokenbodystring用来鉴权你是否有权限生成accessToken
appKeybodystring页面中生成的appKey
appSecretbodystring页面中生成的appSecret

返回格式:

{
  "code": 0,
  "message": "",
  "data": {
    "accessToken":"4tKm1ncg4Pxe9wN4M7dMhQqPITLYfyVShc8BHNNsdLfK7WKFy2Kufq6Sfqh5vzhB",
    "expiresIn":7200
  }
}

返回参数:

  • accessToken:根据appKeyappSecret生成的token。
  • expiresIn:accessToken的有效期。单位:秒

注意事项

  • 开发者需要缓存accessToken,当accessToken失效或过期时,需要重新获取。

  • accessToken的有效期通过返回的expiresIn来传达,正常情况下为7200秒(2小时),有效期超过1200秒(20分钟)重复获取返回相同结果,有效期剩余不超过1200秒(20分钟)获取会返回新的accessToken

  • 页面上重置appSecret会使之前生成的accessToken立即失效。

关于EncodingAESKey

说明

一旦升级到新版本的api,所有通过回调地址回调的消息都会使用EncodingAESKey作为密钥来加密。

句子在推送消息给企业时,如果企业已经升级了api,那么句子会对消息内容做AES加密。这里的加密的消息内容和老版本的消息内容格式保持一致。参考:小组控制台接口(legacy) | 句子秒回API文档open in new window

一些参数说明

  • 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/rfc2315

  • msg:为消息体明文,格式为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":"句子互动"},"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) | 句子秒回API文档open in new window的格式保持一致的

{
  data: {
    messageId: '1224501',
    chatId: '62a2a13e634db54d8183b2fd',
    avatar: 'https://wx.qlogo.cn/mmhead/Q3auHgzwzM4o5pDPk656opaSJrUGlicw0xicWV9GKhKyfyo7RwSdXJiaw/0',
    roomTopic: '',
    roomId: '',
    contactName: '福利官是你2',
    contactId: '7881302521067024',
    payload: { text: '句子互动' },
    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))。

  1. sort的含义是将参数值按照字母字典排序,然后从小到大拼接成一个字符串 sha1处理结果要编码为可见字符,编码的方式是把每字节散列值打印为%02x(即16进制,C printf语法)格式,全部小写

  2. 比较devMsgSignaturemsgSignature是否相等,相等则表示验证通过

解密函数

/**
 * 解密
 *
 * @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: '句子互动' },
    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暂无实际意义

img

  • 对明文字符串加密并Base64编码

msg_encrypt = Base64_Encode(AES_Encrypt(rand_msg))

将明文字符串AESKey加密后,再进行Base64编码,即获得密文msg_encrypt。

回调案例

/message

参数如下:

  • appKey:62ac92c12c4b8587132ab8d9

  • appSecret:YxHi27WiYe5k0dYiVmRFYdolJp9RPGuNmQ5JgaqrMfLKUoB5XV

  • token:62ac92c52c4b8587132ab8da

  • encodingAESKey:25fHA3xB67lRgS2MBwW7w0km1K30ye9PzSnfMGOJslp

原文如下,改格式和原来不加密的消息格式保持一致小组控制台接口(legacy) | 句子秒回API文档open in new window

{
  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'
  }
}
最近更新:
Contributors: windmemory