package utils import ( "crypto/aes" "crypto/cipher" "encoding/base64" "errors" "fmt" ) const ( keyLengthByte = 32 // AES-256 密钥长度(字节) authTagLengthByte = 16 // GCM 认证标签长度(字节) ) // AesUtil 用于微信支付 AES-256-GCM 解密的工具类 type AesUtil struct { aesKey []byte // 32字节的AES密钥 } // NewAesUtil 创建AesUtil实例,验证密钥长度 func NewAesUtil(aesKey string) (*AesUtil, error) { if len(aesKey) != keyLengthByte { return nil, errors.New("无效的ApiV3Key,长度应为32个字节") } return &AesUtil{aesKey: []byte(aesKey)}, nil } // DecryptToString 解密 AEAD_AES_256_GCM 加密的数据 // associatedData: 附加认证数据 // nonceStr: 随机数(12字节) // ciphertext: 加密后的密文(Base64编码) func (a *AesUtil) DecryptToString(associatedData, nonceStr, ciphertext string) (string, error) { // 1. Base64解码密文 cipherBytes, err := base64.StdEncoding.DecodeString(ciphertext) if err != nil { return "", fmt.Errorf("密文Base64解码失败: %w", err) } // 2. 验证密文长度(需包含认证标签) if len(cipherBytes) <= authTagLengthByte { return "", errors.New("密文长度不足,无法解析认证标签") } // 3. 分离密文和认证标签(GCM模式中,认证标签通常附加在密文末尾) ctext := cipherBytes[:len(cipherBytes)-authTagLengthByte] authTag := cipherBytes[len(cipherBytes)-authTagLengthByte:] // 4. 初始化AES-GCM加密器 block, err := aes.NewCipher(a.aesKey) if err != nil { return "", fmt.Errorf("创建AES加密器失败: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return "", fmt.Errorf("创建GCM模式失败: %w", err) } // 5. 构建附加数据(GCM的AAD) additionalData := []byte(associatedData) // 6. 解密(GCM模式会自动验证认证标签) plaintext, err := gcm.Open(nil, []byte(nonceStr), append(ctext, authTag...), additionalData) if err != nil { return "", fmt.Errorf("解密失败(可能是密钥错误或数据被篡改): %w", err) } return string(plaintext), nil } type Resource struct { OriginalType string `json:"original_type"` Algorithm string `json:"algorithm"` Ciphertext string `json:"ciphertext"` // 如支付通知中的 encrypted_data AssociatedData string `json:"associated_data"` // 微信支付回调的附加数据 通常为空字符串或回调相关信息 Nonce string `json:"nonce"` // 12字节字符串 } type WxNotifyBody struct { Id string `json:"id"` CreateTime string `json:"create_time"` ResourceType string `json:"resource_type"` EventType string `json:"event_type"` Summary string `json:"summary"` Resource Resource `json:"resource"` }