AES 字段加密迁配置并兼容旧密文
移除字段加密硬编码主密钥,改为 FIELD_ENCRYPTION_KEY 注入。 保留 FIELD_ENCRYPTION_LEGACY_KEY 透明回退旧密文,新写入统一使用主密钥;生产环境校验主密钥和 legacy 密钥长度、占位符及相等关系,并统一生产环境判断口径。 补充配置与旧密文兼容回归测试。
This commit is contained in:
@@ -13,14 +13,16 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 删除加密密钥常量
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
db *gorm.DB
|
||||
encryptor crypto.Encryptor
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{db: db}
|
||||
func NewRepository(db *gorm.DB, encryptor crypto.Encryptor) *Repository {
|
||||
if encryptor == nil {
|
||||
encryptor = &crypto.MockEncryptor{}
|
||||
}
|
||||
return &Repository{db: db, encryptor: encryptor}
|
||||
}
|
||||
|
||||
func (r *Repository) List(ctx context.Context, userID uint64, page, pageSize int) (*PaginatedResult, error) {
|
||||
@@ -73,7 +75,7 @@ func (r *Repository) FindByID(ctx context.Context, userID, id uint64) (*PaymentA
|
||||
func (r *Repository) Create(ctx context.Context, userID uint64, req CreatePaymentAccountRequest) (*PaymentAccountDTO, error) {
|
||||
db := r.db.WithContext(ctx)
|
||||
// 加密账号
|
||||
encryptedNo, err := crypto.Encrypt(req.AccountNo)
|
||||
encryptedNo, err := r.encryptor.Encrypt(req.AccountNo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -217,7 +219,7 @@ func (r *Repository) ValidateRealname(ctx context.Context, userID uint64, accoun
|
||||
// 验证姓名匹配 - 使用加密字段进行精确匹配
|
||||
if realname.EncryptedName != "" {
|
||||
// 有加密字段,解密后精确匹配
|
||||
decryptedName, err := crypto.Decrypt(realname.EncryptedName)
|
||||
decryptedName, err := r.encryptor.Decrypt(realname.EncryptedName)
|
||||
if err != nil {
|
||||
// 解密失败,降级到前缀匹配
|
||||
return r.validateByMaskedName(realname.MaskedName, accountName)
|
||||
@@ -256,7 +258,7 @@ func (r *Repository) validateByMaskedName(maskedName, accountName string) error
|
||||
|
||||
func (r *Repository) toDTO(account model.UserPaymentAccount) (*PaymentAccountDTO, error) {
|
||||
// 解密账号并脱敏
|
||||
decrypted, err := crypto.Decrypt(account.AccountNo)
|
||||
decrypted, err := r.encryptor.Decrypt(account.AccountNo)
|
||||
if err != nil {
|
||||
decrypted = account.AccountNo // 降级处理
|
||||
}
|
||||
@@ -314,5 +316,5 @@ func (r *Repository) GetDecryptedAccountNo(ctx context.Context, userID, id uint6
|
||||
if err := r.db.WithContext(ctx).Where("id = ? AND user_id = ?", id, userID).First(&account).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
return crypto.Decrypt(account.AccountNo)
|
||||
return r.encryptor.Decrypt(account.AccountNo)
|
||||
}
|
||||
|
||||
@@ -16,14 +16,16 @@ import (
|
||||
const cloudMarketProviderName = "aliyun_cloudmarket"
|
||||
|
||||
type CloudMarketConfig struct {
|
||||
URL string
|
||||
AppCode string
|
||||
URL string
|
||||
AppCode string
|
||||
Encryptor crypto.Encryptor
|
||||
}
|
||||
|
||||
type CloudMarketProvider struct {
|
||||
url string
|
||||
appCode string
|
||||
client *http.Client
|
||||
url string
|
||||
appCode string
|
||||
client *http.Client
|
||||
encryptor crypto.Encryptor
|
||||
}
|
||||
|
||||
type cloudMarketResponse struct {
|
||||
@@ -42,10 +44,15 @@ func NewCloudMarketProvider(cfg CloudMarketConfig) (*CloudMarketProvider, error)
|
||||
if _, err := url.ParseRequestURI(endpoint); err != nil {
|
||||
return nil, fmt.Errorf("realname cloud market url invalid: %w", err)
|
||||
}
|
||||
encryptor := cfg.Encryptor
|
||||
if encryptor == nil {
|
||||
encryptor = &crypto.MockEncryptor{}
|
||||
}
|
||||
return &CloudMarketProvider{
|
||||
url: endpoint,
|
||||
appCode: appCode,
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
url: endpoint,
|
||||
appCode: appCode,
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
encryptor: encryptor,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -108,11 +115,11 @@ func (p *CloudMarketProvider) Start(ctx context.Context, req StartRequest) (Prov
|
||||
}
|
||||
|
||||
// 加密完整信息
|
||||
encryptedName, err := crypto.Encrypt(req.Name)
|
||||
encryptedName, err := p.encryptor.Encrypt(req.Name)
|
||||
if err != nil {
|
||||
return ProviderResult{}, err
|
||||
}
|
||||
encryptedIDNo, err := crypto.Encrypt(req.IDNo)
|
||||
encryptedIDNo, err := p.encryptor.Encrypt(req.IDNo)
|
||||
if err != nil {
|
||||
return ProviderResult{}, err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"hfb_sys/backend/pkg/crypto"
|
||||
)
|
||||
|
||||
func TestCloudMarketProviderStartVerified(t *testing.T) {
|
||||
@@ -27,7 +29,8 @@ func TestCloudMarketProviderStartVerified(t *testing.T) {
|
||||
|
||||
provider, err := NewCloudMarketProvider(CloudMarketConfig{
|
||||
URL: server.URL,
|
||||
AppCode: "test-code",
|
||||
AppCode: "test-code",
|
||||
Encryptor: &crypto.MockEncryptor{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("创建 Provider 失败:%v", err)
|
||||
@@ -60,7 +63,8 @@ func TestCloudMarketProviderStartRejected(t *testing.T) {
|
||||
|
||||
provider, err := NewCloudMarketProvider(CloudMarketConfig{
|
||||
URL: server.URL,
|
||||
AppCode: "test-code",
|
||||
AppCode: "test-code",
|
||||
Encryptor: &crypto.MockEncryptor{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("创建 Provider 失败:%v", err)
|
||||
@@ -90,7 +94,8 @@ func TestCloudMarketProviderStartRateLimited(t *testing.T) {
|
||||
|
||||
provider, err := NewCloudMarketProvider(CloudMarketConfig{
|
||||
URL: server.URL,
|
||||
AppCode: "test-code",
|
||||
AppCode: "test-code",
|
||||
Encryptor: &crypto.MockEncryptor{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("创建 Provider 失败:%v", err)
|
||||
|
||||
@@ -33,10 +33,15 @@ type ProviderResult struct {
|
||||
FailReason string
|
||||
}
|
||||
|
||||
type MockProvider struct{}
|
||||
type MockProvider struct {
|
||||
encryptor crypto.Encryptor
|
||||
}
|
||||
|
||||
func NewMockProvider() *MockProvider {
|
||||
return &MockProvider{}
|
||||
func NewMockProvider(encryptor crypto.Encryptor) *MockProvider {
|
||||
if encryptor == nil {
|
||||
encryptor = &crypto.MockEncryptor{}
|
||||
}
|
||||
return &MockProvider{encryptor: encryptor}
|
||||
}
|
||||
|
||||
func (p *MockProvider) Name() string {
|
||||
@@ -53,11 +58,11 @@ func (p *MockProvider) Start(_ context.Context, req StartRequest) (ProviderResul
|
||||
}
|
||||
|
||||
// 加密完整信息
|
||||
encryptedName, err := crypto.Encrypt(req.Name)
|
||||
encryptedName, err := p.encryptor.Encrypt(req.Name)
|
||||
if err != nil {
|
||||
return ProviderResult{}, err
|
||||
}
|
||||
encryptedIDNo, err := crypto.Encrypt(req.IDNo)
|
||||
encryptedIDNo, err := p.encryptor.Encrypt(req.IDNo)
|
||||
if err != nil {
|
||||
return ProviderResult{}, err
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
"hfb_sys/backend/pkg/crypto"
|
||||
)
|
||||
|
||||
// 转换为用户DTO
|
||||
@@ -64,7 +63,7 @@ func (r *Repository) toDetailDTO(ctx context.Context, w model.WithdrawalRequest)
|
||||
var paymentAccount model.UserPaymentAccount
|
||||
if err := db.First(&paymentAccount, *w.PaymentAccountID).Error; err == nil {
|
||||
// 解密账号
|
||||
decrypted, err := crypto.Decrypt(paymentAccount.AccountNo)
|
||||
decrypted, err := r.encryptor.Decrypt(paymentAccount.AccountNo)
|
||||
if err == nil {
|
||||
fullAccountNo = decrypted
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package withdrawal
|
||||
|
||||
import (
|
||||
"hfb_sys/backend/internal/modules/wallet"
|
||||
"hfb_sys/backend/pkg/crypto"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -9,12 +10,17 @@ import (
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
walletRepo *wallet.Repository
|
||||
encryptor crypto.Encryptor
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB, walletRepo *wallet.Repository) *Repository {
|
||||
func NewRepository(db *gorm.DB, walletRepo *wallet.Repository, encryptor crypto.Encryptor) *Repository {
|
||||
if encryptor == nil {
|
||||
encryptor = &crypto.MockEncryptor{}
|
||||
}
|
||||
return &Repository{
|
||||
db: db,
|
||||
walletRepo: walletRepo,
|
||||
encryptor: encryptor,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user