伙伴云OpenAPI
  1. 订阅
伙伴云OpenAPI
  • 开发指南
    • 开发前必读
    • 申请 API Key
    • 基本概念介绍
    • 数据筛选器
    • 更新日志
  • 工作区
    • 获取工作区列表
      GET
    • 获取工作区成员列表
      POST
  • 表格数据
    • 获取表格列表
      POST
    • 获取表格配置
      POST
    • 查询数据列表
      POST
    • 获取数据详情
      POST
    • 创建数据
      POST
    • 更新数据
      PUT
    • 删除数据
      DELETE
    • 批量创建数据
      POST
    • 批量更新或创建数据
      PUT
    • 批量删除数据
      DELETE
  • 数据仓库数据
    • 获取数据仓库表格列表
      POST
    • 获取数据仓库表格配置
      POST
    • 查询数据列表
      POST
    • 获取数据详情
      POST
    • 批量创建数据
      POST
    • 批量更新数据
      PUT
    • 根据数据ID列表更新成相同数据
      PUT
    • 批量删除数据
      DELETE
  • 流程
    • 获取流程列表
    • 查询流程实例列表
    • 获取流程实例详情
    • 获取流程实例的执行记录
    • 查询流程任务列表
    • 获取流程任务详情
  • 文件
    • 批量获取文件详情
    • 上传文件
  • 订阅
    • 订阅事件概述
    • Encrypt Key 加解密
    • 订阅回调事件
    • 添加订阅
      POST
    • 取消订阅
      POST
    • 获取订阅列表
      POST
  • 服务商
    • 获取 access_token
    • 获取服务商的 api_key
    • 获取服务商信息
    • 设置订阅回调地址
    • 获取订阅回调地址
  1. 订阅

Encrypt Key 加解密

使用 Encrypt Key 加密数据#

如果你对消息内容的安全级别要求较高,可以通过与伙伴云约定密钥的方式进行消息加密。伙伴云推送事件时会使用该密钥对消息内容做对称加密。
企业可在企业后台的“接口授权”中的每个 API Key 上设置回调地址的 Encrypt Key ,服务商可调用“设置订阅回调地址”的接口设置回调地址的 Encrypt Key。
如果设置了 Encrypt Key ,事件推送的请求将会变成:
{
    "encrypted": "oIMJ+3ruZZPVl4NICT//1RGfYT3+EmN7VxptWNIZEan5tLjbJj7eXm4UdU9pJdj6agSUOR3KIg6Le/wKpAqC/qXkJbs5C0Iiep4iXYY5Z4UXx1BavNuRiTtA+ET+4NcMXJjnbTJNV8Mq5lpXOGymSctfSkcPOIcCKeZWq1UG8796WdRyfZaouTb9qWLairar4LhuS0PCWgry7PlTTxQluLHPoHHT0KKXiKZcI8UpO+FNwc1J35ZHffi4ut9PpIC7+JicpYcVbVjWDLKTl67ER2vDbgfgYPacC1kbbXqc7mze2AygtFXy84Lc9yIPYC43BFyoEtMINvdrnCM1c1vcK0JXkemPgMNPCKFGIyghc9T7vTpgFhIQoxLG1y/iTpYkxCXxZ7GeXiF+ntcr4ZQptb30J7TMAnCSCemrFGCdoFfH5sAdtd2OP0NqAH8mxmrOUTwRF8Zqo4Ad9aEOO6ZevQjEDxLbJW+lKehf6qqqDk72JoGW++SyOHhXu4C164s3HZknhSuU5eFPtNR4tUUrowjJ8btJrr+3m69tMR39mkxIZI0BeQiwkjqSSA4PxglljR0zIt7ABA62nNSyU4g9UvydSUZCUTb6GNr1bqx+pD9mY+CEMERXiGOG9n+xkz9ZRZf0qAqQBuotiDYrwF3tAo8vRCB0zifWAX60IUmNkM+i0kLXtCS+1aXMRYbu67tH+FonQbu+PiF8o9GJn1UazsH8vWK5E0wiOARw3X/cK2AxD+n/IGamUJZykPrKLrBeqQoOfAEqeDgu38IqZGxqATVRHw6NLEaLpsq/E62e1khd0/zo837WqycnfhtipECgi2VRZzYyK9DtGSUnloCrE0pnEhEg42pO0AsaiL74oT5HjIm+KZrzYM6FGxhsRjG5yBvfq3ITapNPv7f3qvhtwSSQrFA2VDtWMbL+ZiZNztDhV0r6hb7ezBbsJwavSOmBCIB9yJwZW51QVQ9fAmRLEa+tiDYD4GU/SsEc/+NSnIiEpIvqE8TP12572cj+l0XynNVjEiliriiniKBmr9h63u5R8aPyxtb7oCfOG8Kg1PwWuSUthuELpqze755w4hRO31+Z6AJhrILXNTokxoaq22a2thTHX/sQsEdub5UAKrUzODD0f5mEl4bkRXYQsfSAodxvgeM6IOUhoqIP4/D6whRbZrIfG2Sw4uJC3lKQTcpQIwc0poLKS3mqlA2t8GRyhQzxVUBHf9X2ZYTRXQFw00R274Xvp3uAsCgY1Gf16P2Rt2vCHyhcBNDbXJ+eZ1LCo5f87Z8aVAKaaEBVm+21QJGesLUHUv8Ll9Zany5ctWidk6a66tYsNjQRBsYqpnRfwVmNyGHmJxaPty0KUMTMLUFXaA+vF9lQ70RtvhuOLRtn5AqhmZ0xujP249cjyDrY3lZ9iTla7hveWvAx9ZyX9vjMumpMjy+c4KUz2cfYvFkzgsm8921jaHWJP59kmCjHAV2EuoibU7cRczHu4CCMjBOouMiAI/MQ8TBoHdrA5sYnS0seVVWEM1x19OOxtd0hf27nL9XejlNooTadhiDe40JOKbd3z7nVOaCgiJNagL/hNIRec9pktZ/V/5x1n8lamR53OXiSl8Q+SgGsMxF7FoBQ8yBroNAbdIvcUy0r7yzgRX6Fv9D9B8b/YqHyNcW/xU5Ebd9ZC3G/jtmLrVwxr5k8QIM2M9/W2zL9CUunmXMCNBdX/do+IN/HK0JdS3VRMmjgEICgBCVep1YyeoDYtSGxqf/JfHmFrvtydaz29RXBMpBFLm18G0SozdM8ub1wv+gvZgG5jDy7/vXfLjIL4UJH8LS5A0zwTift8FKFSlKE8nuxlB4cMO03Frz6ionOcbrkJsiPFAR51E1N9KQ0WnZ3cgajwm0fzCjlVhyjhfm/N3pjA6jPOf3NdBi9+q8wB9WhbIkl5OTt0OxT47Gz80/Mkz2QAxbOr7TcnoNAWWx63xl2tHwYv7iKKUmHwUPe"
}
这段密文对应的 Encrypt Key 为 thisisakey2022。
解密后就会得到:
{
    "schema": "1.0",
    "header": { 
        "event_id": "f7984f25108f8137722bb63cee927e66",
        "event_type": "item.create"
    },
    "data": {
        "table_id": "2100000000000001",
        "bulk": false,
        "item": {
            "item_id": "2300000000000001",
            "title": "数据标题",
            "created_by": {
                "user_id": "1024",
                "name": "接口",
                "avatar": "https://hb-v4-public-oss.huoban.com/user_avatar/317474452/0?imageMogr2/thumbnail/128x128>"
            },
            "created_on": "2021-12-15 20:45:25",
            "updated_by": {
                "user_id": "1024",
                "name": "接口",
                "avatar": "https://hb-v4-public-oss.huoban.com/user_avatar/317474452/0?imageMogr2/thumbnail/128x128>"
            },
            "updated_on": "2022-03-31 18:33:15",
            "fields": {
                "2200000137788628": "单行文本",
                "2200000137788629": "多行文本1<br/>多行文本2<br/>多行文本3",
                "2200000137788630": "<p><b>富文本1</b></p><p><font color=\"#dc0000\">富文本2</font></p><ol><li>富文本3</li><li>富文本4</li></ol>",
                "2200000137788631": "18612345678",
                "2200000137788632": "6954432710706",
                "2200000137788634": 23456,
                "2200000137788635": 0.85,
                "2200000137788642": 234.56,
                "2200000137788643": "2022-03-03",
                "2200000137788644": "2022-03-03 00:00:00",
                "2200000137788645": 23456,
                "2200000137788646": 0.85,
                "2200000137788647": "2022-03-03",
                "2200000137788648": "2022-03-03 00:00:00"
            }
        }
    }
}

加密方式#

事件内容采用标准的 AES-256-CBC 加密,加密过程:
1.
使用 SHA256 对 Encrypt Key 进行哈希得到密钥 key ;
2.
使用 PKCS7Padding 方式将事件内容进行填充;
3.
生成 16 个字节的随机数作为初始向量 iv ;
4.
使用 iv 和 key 对事件内容加密得到 encrypted_data ;
5.
接收到的密文 encrypted 为 base64(iv+encrypted_data) 。

解密示例代码#

Java
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class Decrypt {
    
    public static void main(String[] args) throws Exception {
        Decrypt decrypt = new Decrypt("thisisakey2022");
        System.out.println(decrypt.decrypt("Krus6gVY79RpG6NfPtsQuLMjMMAKd6zB1zjVQg/eBr4=")); // hello world
    }
    
    private byte[] keyBytes;
    
    public Decrypt(String key) {
        MessageDigest digest = null;
        try {
            digest = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            // exception
        }
        keyBytes = digest.digest(key.getBytes(StandardCharsets.UTF_8));
    }
    
    public String decrypt(String base64) throws Exception {
        // base64 decode
        byte[] decode = Base64.getDecoder().decode(base64);
        Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING");
        // iv
        byte[] iv = new byte[16];
        System.arraycopy(decode, 0, iv, 0, 16);
        byte[] data = new byte[decode.length - 16];
        System.arraycopy(decode, 16, data, 0, data.length);
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new IvParameterSpec(iv));
        byte[] result = cipher.doFinal(data);
        if (result.length > 0) {
            int len = result.length - 1;
            for (; len >= 0 && result[len] <= 16; len--) {
            }
            if (len != result.length - 1) {
                byte[] tmp = new byte[len + 1];
                System.arraycopy(result, 0, tmp, 0, len + 1);
                result = tmp;
            }
        }
        return new String(result, StandardCharsets.UTF_8);
    }
}
Golang
package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/sha256"
    "encoding/base64"
    "errors"
    "fmt"
)

func main() {
    key := "thisisakey2022"
    encrypted := "Krus6gVY79RpG6NfPtsQuLMjMMAKd6zB1zjVQg/eBr4=" // hello world
    // base64 decode
    base64Decodedbuf, err := base64.StdEncoding.DecodeString(encrypted)
    if err != nil {
        panic(err)
    }
    
    plaintextByts, err := Decrypt(base64Decodedbuf, []byte(key))
    if err != nil {
        panic(err)
    }
    fmt.Println(string(plaintextByts))
}

func Decrypt(buf, key []byte) (byts []byte, err error) {
    if len(buf) < aes.BlockSize {
        err = errors.New("cipher length err")
        return
    }
    keySum := sha256.Sum256(key)
    block, err := aes.NewCipher(keySum[:sha256.Size])
    if err != nil {
        return
    }
    iv := buf[:aes.BlockSize]
    buf = buf[aes.BlockSize:]
    if len(buf)%aes.BlockSize != 0 {
        err = errors.New("cipher text length error")
        return
    }
    mode := cipher.NewCBCDecrypter(block, iv)
    mode.CryptBlocks(buf, buf)

    m := len(buf)
    bufLast := buf[m-1]
    if bufLast > byte(aes.BlockSize) || m-int(bufLast) < 0 {
        err = errors.New("unpadding error")
        return
    }
    m = m - int(bufLast)
    byts = buf[0:m]
    return
}
Python
import hashlib
import base64
from Crypto.Cipher import AES

class AES256CBCCipher(object):
    def __init__(self, key):
        self.key = hashlib.sha256(key.encode('utf8')).digest()
    @staticmethod
    def _unpad(s):
        return s[:-ord(s[len(s) - 1:])]
    def decrypt(self, encode):
        iv = encode[:AES.block_size]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return self._unpad(cipher.decrypt(encode[AES.block_size:]))
    def decrypt_string(self, encode):
        encode = base64.b64decode(encode)
        return self.decrypt(encode).decode('utf8')

encrypt = "Krus6gVY79RpG6NfPtsQuLMjMMAKd6zB1zjVQg/eBr4="
cipher = AES256CBCCipher("thisisakey2022")
print(format(cipher.decrypt_string(encrypt))) # hello world
Node.js
上一页
订阅事件概述
下一页
订阅回调事件
Built with