package encrypt import ( "context" "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "testing" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gcfg" ) func TestDecryptFieldAcceptsCustomerPaymentKeys(t *testing.T) { ctx := context.Background() current := "current-finance-master-key-with-at-least-32-characters" previous := "previous-finance-master-key-with-at-least-32-characters" setFieldTestConfig(t, map[string]interface{}{"dataKey": "admin-legacy-key", "financeMasterKey": current, "financeLegacyMasterKeys": []string{previous}}) for _, master := range []string{current, previous} { encoded := customerPaymentFixture(t, master, "6222000000001234") plain, err := DecryptField(ctx, encoded) if err != nil || plain != "6222000000001234" { t.Fatalf("customer payment must decrypt with current or previous key: %v", err) } } } func TestPaymentWritesRemainReadableByCustomerService(t *testing.T) { ctx := context.Background() master := "current-finance-master-key-with-at-least-32-characters" setFieldTestConfig(t, map[string]interface{}{"dataKey": "admin-legacy-key", "financeMasterKey": master}) const value = "6222000000001234" encoded, err := EncryptPaymentField(ctx, value) if err != nil { t.Fatal(err) } payload, err := base64.RawStdEncoding.DecodeString(encoded) if err != nil { t.Fatal("payment edit must use the customer's standard Base64 format") } mac := hmac.New(sha256.New, []byte(master)) _, _ = mac.Write([]byte("olons-finance:data-encryption:v1")) block, _ := aes.NewCipher(mac.Sum(nil)) aead, _ := cipher.NewGCM(block) plain, err := aead.Open(nil, payload[:aead.NonceSize()], payload[aead.NonceSize():], nil) if err != nil || string(plain) != value { t.Fatalf("customer service cannot read admin edit: %v", err) } actualHash, err := HashPaymentField(ctx, value) if err != nil { t.Fatal(err) } mac = hmac.New(sha256.New, []byte(master)) _, _ = mac.Write([]byte("olons-finance:lookup-hmac:v1")) lookup := hmac.New(sha256.New, mac.Sum(nil)) _, _ = lookup.Write([]byte(value)) if actualHash != hex.EncodeToString(lookup.Sum(nil)) { t.Fatal("admin lookup hash must match customer service") } } func TestDecryptFieldKeepsLegacyHotGoDataReadable(t *testing.T) { ctx := context.Background() setFieldTestConfig(t, map[string]interface{}{"dataKey": "admin-legacy-key", "financeMasterKey": "current-finance-master-key-with-at-least-32-characters"}) encoded, err := EncryptField(ctx, "旧资料") if err != nil { t.Fatal(err) } plain, err := DecryptField(ctx, encoded) if err != nil || plain != "旧资料" { t.Fatalf("legacy HotGo record unreadable: %v", err) } for _, invalid := range []string{"DEMO-NOT-CIPHERTEXT", "AAAA", customerPaymentFixture(t, "unknown-master-key-with-at-least-32-characters", "姓名")} { if _, err := DecryptField(ctx, invalid); err == nil { t.Fatal("invalid or unknown-key records must not be reported as plaintext") } } } func setFieldTestConfig(t *testing.T, security map[string]interface{}) { t.Helper() data, err := json.Marshal(map[string]interface{}{"security": security}) if err != nil { t.Fatal(err) } adapter, err := gcfg.NewAdapterContent(string(data)) if err != nil { t.Fatal(err) } previous := g.Cfg().GetAdapter() g.Cfg().SetAdapter(adapter) t.Cleanup(func() { g.Cfg().SetAdapter(previous) }) } func customerPaymentFixture(t *testing.T, master, value string) string { t.Helper() mac := hmac.New(sha256.New, []byte(master)) _, _ = mac.Write([]byte("olons-finance:data-encryption:v1")) block, err := aes.NewCipher(mac.Sum(nil)) if err != nil { t.Fatal(err) } aead, err := cipher.NewGCM(block) if err != nil { t.Fatal(err) } nonce := []byte{251, 255, 239, 255, 251, 255, 239, 255, 251, 255, 239, 255} return base64.RawStdEncoding.EncodeToString(aead.Seal(nonce, nonce, []byte(value), nil)) }