事件格式-签名&解密
业务接收到请求后,需要做签名验证、解密等操作
1. 签名
用于验证回调请求数据无篡改
验证签名:
token = x-request-token (请求头里获取)
app_id = x-request-app-id (请求头里获取)
timestamp = x-request-timestamp (请求头里获取)
body = request_body(请求的body参数) // +:表示字符串拼接 检查
sha256(app_id+body+timestamp) == token 是否成立
2. 解密
当 x-request-need-encrypt(请求头) 为 true
业务方需要基于AES-256-CBC 算法,使用 secret(后台获取) 解密 request_body 里的 encrypt (使用json 解析 request_body 后,获取encrypt的值)
2.1.1 需要解密时,request_body格式
//x-request-need-encrypt 为true 需要解密
secretKey=secret(后台获取)
//需要解密的示例值
request_body=`{"encrypt":"-iGBkTiANHIebyc02rD67ih5TW48AnFi2HGAI-MXlC-K07nCsVU8k0DtdzgUEp7uUgg5D2VOGE2TV6x_p2w-g5r7OK5Qv4f5ZmY0tdWosD1Nzbu3wNyZctn9y9IlAD8ZZ1dLNxI039LyNAX1hkTAN99TDmso4f3w1eIQ3qsr0rCfpBfqAsp_ndl_rQXahZXofW2wGqOLuewmTjPBWj5jgJkMnJxURGiGcdDC7BjLW_tiHMGcWm6LcVPASMIAac-EyQPr7bvTbQ0NvYK0Ikzozgzia5qIQqaYcoet5dMn4TY"}`
//获取密文
requestBody:=map[string]string{} json.unmarshal([]byte(request_body),&requestBody) encrypt:=requestBody["encrypt"]
//解密
Decrypt(encrypt,secretKey)
//解密后的格式
{"schema":"1.0","header":{"event_id":"814f6a52239171a4a47387df2d41f11e","create_time":1739763187139,"event_type":"im.message.group_at.receive_v1","app_id":"robot_mibxy8f6mfstpmqp"},"event":{"open_id":"ou_8a61ec4e2c7232294c498fd70f5649d0","open_message_id":"190b20c5a501ed123826c61bc7e258bf-202502-0","create_time":0,"send_time":1739763186871,"send_id":"BxgMFUUoCQZUHh5LX0QADxpBCAsLBwUFGwo","content_type":114,"raw_content_type":140,"sender_nickname":"如易","sender_face_url":"https://s1-imfile.im30.net/uwnz1wdo6fgs9rmii5xfzwwjye","group_id":"100001141t0","content":"{\"text\":\"@测试机器人5-如易 111\",\"json\":{\"content\":[{\"content\":[{\"attrs\":{\"id\":\"HR89XSslFxMYAgNeAGkZXgcaRxUQHRobBg4AOQgOUwEdWlAFVAcYFQ\",\"label\":\"测试机器人5-如易\"},\"type\":\"mention\"},{\"text\":\" 111\",\"type\":\"text\"}],\"type\":\"paragraph\"}],\"type\":\"doc\"},\"atUserIDList\":[\"HR89XSslFxMYAgNeAGkZXgcaRxUQHRobBg4AOQgOUwEdWlAFVAcYFQ\"]}","quote_content_type":101,"quote_send_id":"BxgMFUUoCQZUHh5LX0QADxpBCAsLBwUFGwo","quote_sender_nickname":"如易","quote_sender_face_url":"https://s1-imfile.im30.net/uwnz1wdo6fgs9rmii5xfzwwjye","quote_content":"@奥托 引用的会话消息\n[图片]","quote_message_id":"cf7437c5c277b87c0b407cf9ad3898af-202502-0"}}
2.1.2 不需要解密时,request_body 格式
//x-request-need-encrypt 为 false 不需要解密
request_body=`{"schema":"1.0","header":{"event_id":"eec4bbda12765a9f6946483403f32913","create_time":1737110488603,"event_type":"application.bot.verify_callback_url","app_id":"robot_peozr1m9cq3mox8p"},"event":null}`
3. 加解密算法
加解密算法 AES-256-CBC
使用 SHA256 对
Encrypt Key进行哈希得到密钥key。使用 PKCS7Padding 方式将事件内容进行填充。
生成 16 字节的随机数作为初始向量
iv。使用
iv和key对事件内容加密得到encrypted_event。应用收到的密文为
base64(iv+encrypted_event)。
package utils
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
"strings"
)
func Encrypt(plaintext []byte, encryptKey string) (string, error) {
// 使用SHA256对加密密钥进行哈希得到密钥key
key := sha256.Sum256([]byte(encryptKey))
// 使用PKCS7Padding方式对明文内容进行填充
blockSize := aes.BlockSize
padding := blockSize - len(plaintext)%blockSize
padText := append(plaintext, bytes.Repeat([]byte{byte(padding)}, padding)...)
// 生成16字节的随机数作为初始向量iv
iv := make([]byte, blockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", fmt.Errorf("io.ReadFull Error[%v]", err)
}
// 创建AES加密算法实例
block, err := aes.NewCipher(key[:])
if err != nil {
return "", fmt.Errorf("aes.NewCipher Error[%v]", err)
}
// 使用CBC模式进行加密
mode := cipher.NewCBCEncrypter(block, iv)
encrypted := make([]byte, len(padText))
mode.CryptBlocks(encrypted, padText)
// 将iv和加密后的内容拼接,并进行base64编码
ciphertext := append(iv, encrypted...)
ciphertextBase64 := base64.RawURLEncoding.EncodeToString(ciphertext)
return ciphertextBase64, nil
}
// PKCS7Padding 填充
func PKCS7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
func Decrypt(encrypt string, key string) (string, error) {
buf, err := base64.RawURLEncoding.DecodeString(encrypt)
if err != nil {
return "", fmt.Errorf("base64StdEncode Error[%v]", err)
}
if len(buf) < aes.BlockSize {
return "", errors.New("cipher too short")
}
keyBs := sha256.Sum256([]byte(key))
block, err := aes.NewCipher(keyBs[:sha256.Size])
if err != nil {
return "", fmt.Errorf("AESNewCipher Error[%v]", err)
}
iv := buf[:aes.BlockSize]
buf = buf[aes.BlockSize:]
if len(buf)%aes.BlockSize != 0 {
return "", errors.New("ciphertext is not a multiple of the block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(buf, buf)
n := strings.Index(string(buf), "{")
if n == -1 {
n = 0
}
m := strings.LastIndex(string(buf), "}")
if m == -1 {
m = len(buf) - 1
}
return string(buf[n : m+1]), nil
}
更新时间:2026-03-26