package encrypt import ( "context" "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "io" "strings" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" ) // financeFieldKey uses the same key derivation as the customer-facing finance service. // An empty configuration retains the existing HotGo format for older installations. func financeFieldKey(ctx context.Context, purpose string) ([]byte, error) { master := strings.TrimSpace(g.Cfg().MustGet(ctx, "security.financeMasterKey", "").String()) if master != "" { if len([]byte(master)) < 32 { return nil, gerror.New("收款资料密钥配置不正确") } return deriveFinanceFieldKey(master, purpose), nil } name := "security.financeDataEncryptionKey" if purpose == "lookup-hmac" { name = "security.financeLookupHmacKey" } encoded := strings.TrimSpace(g.Cfg().MustGet(ctx, name, "").String()) if encoded == "" { return nil, nil } key, err := decodeFieldPayload(encoded) if err != nil || (purpose == "data-encryption" && len(key) != 32) || (purpose == "lookup-hmac" && len(key) < 32) { return nil, gerror.New("收款资料密钥配置不正确") } return key, nil } func deriveFinanceFieldKey(master, purpose string) []byte { mac := hmac.New(sha256.New, []byte(master)) _, _ = mac.Write([]byte("olons-finance:" + purpose + ":v1")) return mac.Sum(nil) } func financeFieldReadKeys(ctx context.Context) [][]byte { keys := make([][]byte, 0) if key, err := financeFieldKey(ctx, "data-encryption"); err == nil && len(key) > 0 { keys = append(keys, key) } for _, master := range g.Cfg().MustGet(ctx, "security.financeLegacyMasterKeys", []string{}).Strings() { if master = strings.TrimSpace(master); len([]byte(master)) >= 32 { keys = append(keys, deriveFinanceFieldKey(master, "data-encryption")) } } return keys } func decodeFieldPayload(value string) ([]byte, error) { value = strings.TrimSpace(value) for _, encoding := range []*base64.Encoding{base64.RawURLEncoding, base64.RawStdEncoding, base64.URLEncoding, base64.StdEncoding} { if raw, err := encoding.DecodeString(value); err == nil { return raw, nil } } return nil, gerror.New("敏感字段密文格式错误") } // EncryptPaymentField keeps new admin edits readable by the customer-facing service. func EncryptPaymentField(ctx context.Context, plaintext string) (string, error) { if plaintext == "" { return "", nil } key, err := financeFieldKey(ctx, "data-encryption") if err != nil { return "", err } if len(key) == 0 { return EncryptField(ctx, plaintext) } block, err := aes.NewCipher(key) if err != nil { return "", gerror.Wrap(err, "初始化收款资料加密失败") } aead, err := cipher.NewGCM(block) if err != nil { return "", gerror.Wrap(err, "初始化收款资料加密失败") } nonce := make([]byte, aead.NonceSize()) if _, err = io.ReadFull(rand.Reader, nonce); err != nil { return "", gerror.Wrap(err, "生成收款资料随机数失败") } return base64.RawStdEncoding.EncodeToString(aead.Seal(nonce, nonce, []byte(plaintext), nil)), nil } func HashPaymentField(ctx context.Context, value string) (string, error) { key, err := financeFieldKey(ctx, "lookup-hmac") if err != nil { return "", err } if len(key) == 0 { return HashField(ctx, value) } mac := hmac.New(sha256.New, key) _, _ = mac.Write([]byte(strings.TrimSpace(value))) return hex.EncodeToString(mac.Sum(nil)), nil }