143 lines
4.0 KiB
Go
143 lines
4.0 KiB
Go
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
|
||
} |