package wechatmp import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/base64" "errors" "io" "strings" beego "github.com/beego/beego/v2/server/web" ) // ============================================================= // 微信公众号敏感字段(AppSecret / EncodingAESKey)的对称加密与掩码 // // yz_platform_wechat_mp_config.app_secret / aes_key 以 AES-256-GCM 加密后存储, // 格式:base64( "YZW1:" + nonce + ciphertext )。 // 密钥来源:app.conf 的 wechat_mp_secret_key(任意长度字符串,内部做 SHA-256 派生); // 未配置时退回内置兜底密钥(保证开箱可用),上线前建议在 app.conf 配置该值。 // 注意:更换密钥后,历史密文将无法解密,需要重新保存一次配置。 // ============================================================= const ( cipherPrefix = "YZW1:" fallbackSecretKey = "yunzer_wechat_mp_default_secret_key_v1" ) func configKey() []byte { raw, _ := beego.AppConfig.String("wechat_mp_secret_key") if strings.TrimSpace(raw) == "" { raw = fallbackSecretKey } sum := sha256.Sum256([]byte(raw)) return sum[:] } // EncryptSecret 加密敏感字段;空串原样返回 func EncryptSecret(plain string) (string, error) { if plain == "" { return "", nil } block, err := aes.NewCipher(configKey()) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } nonce := make([]byte, gcm.NonceSize()) if _, err = io.ReadFull(rand.Reader, nonce); err != nil { return "", err } ciphertext := gcm.Seal(nil, nonce, []byte(plain), nil) buf := append([]byte(cipherPrefix), nonce...) buf = append(buf, ciphertext...) return base64.StdEncoding.EncodeToString(buf), nil } // DecryptSecret 解密敏感字段;空串原样返回 func DecryptSecret(encoded string) (string, error) { if encoded == "" { return "", nil } raw, err := base64.StdEncoding.DecodeString(encoded) if err != nil { return "", errors.New("密文不是有效的 base64") } if len(raw) < len(cipherPrefix) || string(raw[:len(cipherPrefix)]) != cipherPrefix { return "", errors.New("密文格式不正确(缺少加密前缀)") } block, err := aes.NewCipher(configKey()) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } nonce := raw[len(cipherPrefix) : len(cipherPrefix)+gcm.NonceSize()] ciphertext := raw[len(cipherPrefix)+gcm.NonceSize():] plain, err := gcm.Open(nil, nonce, ciphertext, nil) if err != nil { return "", errors.New("解密失败(wechat_mp_secret_key 是否被更换过?)") } return string(plain), nil } // MaskSecret 生成掩码:保留末 4 位 func MaskSecret(v string) string { if v == "" { return "" } if len(v) <= 4 { return "******" } return "******" + v[len(v)-4:] } // IsMasked 判断前端回传的值是否仍是掩码(表示「不修改原值」) func IsMasked(v string) bool { return strings.HasPrefix(v, "******") }