diff --git a/server/manifest/config/config.example.yaml b/server/manifest/config/config.example.yaml index baefc67..70f5dca 100644 --- a/server/manifest/config/config.example.yaml +++ b/server/manifest/config/config.example.yaml @@ -195,6 +195,8 @@ token: # 业务敏感字段保护。生产环境必须替换为独立的高强度密钥,并通过密钥管理系统注入。 security: dataKey: "change-me-before-production" + # 迁移前的 HotGo dataKey,仅用于兼容读取,不能删除仍有存量密文使用的密钥。 + legacyDataKeys: [] # 与 C 端收款资料使用相同配置;历史主密钥只用于读取,不修改原密文。 financeMasterKey: "" financeLegacyMasterKeys: [] diff --git a/server/utility/encrypt/field.go b/server/utility/encrypt/field.go index 794ba7c..d9e2e21 100644 --- a/server/utility/encrypt/field.go +++ b/server/utility/encrypt/field.go @@ -65,6 +65,13 @@ func DecryptField(ctx context.Context, encoded string) (string, error) { if key, keyErr := fieldKey(ctx); keyErr == nil { keys = append(keys, key) } + // Imported HotGo records can use an older dataKey; never use these keys for new writes. + for _, secret := range g.Cfg().MustGet(ctx, "security.legacyDataKeys", []string{}).Strings() { + if secret = strings.TrimSpace(secret); secret != "" { + sum := sha256.Sum256([]byte(secret)) + keys = append(keys, sum[:]) + } + } for _, key := range keys { block, keyErr := aes.NewCipher(key) if keyErr != nil { diff --git a/server/utility/encrypt/finance_field_test.go b/server/utility/encrypt/finance_field_test.go index 774ee22..c0e2068 100644 --- a/server/utility/encrypt/finance_field_test.go +++ b/server/utility/encrypt/finance_field_test.go @@ -81,6 +81,28 @@ func TestDecryptFieldKeepsLegacyHotGoDataReadable(t *testing.T) { } } +func TestDecryptFieldReadsImportedHotGoKeyWithoutChangingCurrentKey(t *testing.T) { + ctx := context.Background() + setFieldTestConfig(t, map[string]interface{}{"dataKey": "imported-original-data-key"}) + encoded, err := EncryptField(ctx, "迁移前的资料") + if err != nil { + t.Fatal(err) + } + setFieldTestConfig(t, map[string]interface{}{"dataKey": "current-data-key", "legacyDataKeys": []string{"imported-original-data-key"}, "financeMasterKey": "current-finance-master-key-with-at-least-32-characters"}) + plain, err := DecryptField(ctx, encoded) + if err != nil || plain != "迁移前的资料" { + t.Fatalf("imported record unreadable: %v", err) + } + current, err := EncryptField(ctx, "当前资料") + if err != nil { + t.Fatal(err) + } + plain, err = DecryptField(ctx, current) + if err != nil || plain != "当前资料" { + t.Fatalf("current key was not preserved: %v", err) + } +} + func setFieldTestConfig(t *testing.T, security map[string]interface{}) { t.Helper() data, err := json.Marshal(map[string]interface{}{"security": security})