邹方成 2e86f8ae42 feat(wechat): 增加微信小程序用户数据解密功能
添加对微信小程序加密用户数据的解密支持,包括签名验证和解密用户信息
更新swagger文档以反映新的API字段和数据结构
2025-10-18 23:08:55 +08:00

143 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package wechat
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
)
// DecryptedUserInfo 解密后的用户信息结构
type DecryptedUserInfo struct {
OpenID string `json:"openId"`
NickName string `json:"nickName"`
Gender int `json:"gender"`
City string `json:"city"`
Province string `json:"province"`
Country string `json:"country"`
AvatarURL string `json:"avatarUrl"`
UnionID string `json:"unionId,omitempty"`
Watermark Watermark `json:"watermark"`
}
// Watermark 数据水印
type Watermark struct {
AppID string `json:"appid"`
Timestamp int64 `json:"timestamp"`
}
// DecryptData 解密微信小程序加密数据
// sessionKey: 会话密钥(从 code2session 接口获取)
// encryptedData: 加密数据Base64 编码)
// iv: 初始向量Base64 编码)
// 返回解密后的 JSON 字符串
func DecryptData(sessionKey, encryptedData, iv string) (string, error) {
// 1. Base64 解码 session_key
aesKey, err := base64.StdEncoding.DecodeString(sessionKey)
if err != nil {
return "", fmt.Errorf("session_key base64 解码失败: %v", err)
}
// 2. Base64 解码加密数据
cipherText, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return "", fmt.Errorf("encryptedData base64 解码失败: %v", err)
}
// 3. Base64 解码初始向量
ivBytes, err := base64.StdEncoding.DecodeString(iv)
if err != nil {
return "", fmt.Errorf("iv base64 解码失败: %v", err)
}
// 4. 验证密钥长度AES-128 需要 16 字节)
if len(aesKey) != 16 {
return "", fmt.Errorf("session_key 长度错误,期望 16 字节,实际 %d 字节", len(aesKey))
}
// 5. 验证 IV 长度AES 块大小为 16 字节)
if len(ivBytes) != 16 {
return "", fmt.Errorf("iv 长度错误,期望 16 字节,实际 %d 字节", len(ivBytes))
}
// 6. 验证密文长度(必须是 AES 块大小的倍数)
if len(cipherText)%aes.BlockSize != 0 {
return "", fmt.Errorf("密文长度错误,必须是 %d 字节的倍数,实际 %d 字节", aes.BlockSize, len(cipherText))
}
// 7. 创建 AES 解密器
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", fmt.Errorf("创建 AES 解密器失败: %v", err)
}
// 8. 创建 CBC 模式解密器
mode := cipher.NewCBCDecrypter(block, ivBytes)
// 9. 解密数据
decrypted := make([]byte, len(cipherText))
mode.CryptBlocks(decrypted, cipherText)
// 10. 去除 PKCS#7 填充
decrypted, err = pkcs7Unpad(decrypted)
if err != nil {
return "", fmt.Errorf("去除填充失败: %v", err)
}
return string(decrypted), nil
}
// DecryptUserInfo 解密用户信息并返回结构化数据
func DecryptUserInfo(sessionKey, encryptedData, iv string) (*DecryptedUserInfo, error) {
decryptedJSON, err := DecryptData(sessionKey, encryptedData, iv)
if err != nil {
return nil, err
}
var userInfo DecryptedUserInfo
if err := json.Unmarshal([]byte(decryptedJSON), &userInfo); err != nil {
return nil, fmt.Errorf("解析用户信息 JSON 失败: %v", err)
}
return &userInfo, nil
}
// VerifySignature 验证数据签名
// rawData: 原始数据
// signature: 签名
// sessionKey: 会话密钥
func VerifySignature(rawData, signature, sessionKey string) bool {
// 计算签名sha1(rawData + sessionKey)
h := sha1.New()
h.Write([]byte(rawData + sessionKey))
expectedSignature := hex.EncodeToString(h.Sum(nil))
return expectedSignature == signature
}
// pkcs7Unpad 去除 PKCS#7 填充
func pkcs7Unpad(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, fmt.Errorf("数据为空")
}
// 获取填充长度
padding := int(data[len(data)-1])
// 验证填充长度
if padding > len(data) || padding == 0 {
return nil, fmt.Errorf("无效的填充长度: %d", padding)
}
// 验证填充字节
for i := len(data) - padding; i < len(data); i++ {
if data[i] != byte(padding) {
return nil, fmt.Errorf("无效的填充字节")
}
}
return data[:len(data)-padding], nil
}