107 lines
3.1 KiB
Go
107 lines
3.1 KiB
Go
package payment
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
|
|
beego "github.com/beego/beego/v2/server/web"
|
|
)
|
|
|
|
// =============================================================
|
|
// 渠道敏感参数的对称加密与掩码
|
|
//
|
|
// yz_platform_payment_channel.config_json 中的密钥/私钥/Secret 以 AES-256-GCM 加密后存储,
|
|
// 格式:base64( "YZP1:" + nonce + ciphertext )。
|
|
// 密钥来源:app.conf 的 payment_secret_key(任意长度字符串,内部做 SHA-256 派生);
|
|
// 未配置时退回内置兜底密钥(保证开箱可用),上线前务必在 app.conf 配置该值,
|
|
// 且注意:更换密钥后,历史密文将无法解密,需要重新保存各渠道配置。
|
|
// =============================================================
|
|
|
|
const configCipherPrefix = "YZP1:"
|
|
|
|
// fallbackSecretKey 内置兜底密钥,仅用于开发/未配置场景
|
|
const fallbackSecretKey = "yunzer_payment_default_secret_key_v1"
|
|
|
|
func configKey() []byte {
|
|
raw, _ := beego.AppConfig.String("payment_secret_key")
|
|
if strings.TrimSpace(raw) == "" {
|
|
raw = fallbackSecretKey
|
|
}
|
|
sum := sha256.Sum256([]byte(raw))
|
|
return sum[:]
|
|
}
|
|
|
|
// EncryptConfig 加密渠道参数 JSON
|
|
func EncryptConfig(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(configCipherPrefix), nonce...)
|
|
buf = append(buf, ciphertext...)
|
|
return base64.StdEncoding.EncodeToString(buf), nil
|
|
}
|
|
|
|
// DecryptConfig 解密渠道参数 JSON;空串原样返回
|
|
func DecryptConfig(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(configCipherPrefix) || string(raw[:len(configCipherPrefix)]) != configCipherPrefix {
|
|
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(configCipherPrefix) : len(configCipherPrefix)+gcm.NonceSize()]
|
|
ciphertext := raw[len(configCipherPrefix)+gcm.NonceSize():]
|
|
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
|
|
if err != nil {
|
|
return "", errors.New("渠道参数解密失败(payment_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, "******")
|
|
}
|