diff --git a/internal/domains/api/services/processors/flxg/flxghb4f_processor.go b/internal/domains/api/services/processors/flxg/flxghb4f_processor.go index 307d36b..0518e00 100644 --- a/internal/domains/api/services/processors/flxg/flxghb4f_processor.go +++ b/internal/domains/api/services/processors/flxg/flxghb4f_processor.go @@ -22,12 +22,12 @@ func ProcessFLXGHB4FRequest(ctx context.Context, params []byte, deps *processors } // 使用 MD5 加密 name 和 idCard - encryptedName := "MD5:" + huibo.MD5Encrypt(paramsDto.Name, deps.HuiboService.GetConfig().AppKey) - encryptedIDCard := "MD5:" + huibo.MD5Encrypt(paramsDto.IDCard, deps.HuiboService.GetConfig().AppKey) + // encryptedName := "MD5:" + huibo.MD5Encrypt(paramsDto.Name, deps.HuiboService.GetConfig().AppKey) + // encryptedIDCard := "MD5:" + huibo.MD5Encrypt(paramsDto.IDCard, deps.HuiboService.GetConfig().AppKey) reqdata := map[string]interface{}{ - "name": encryptedName, - "idCard": encryptedIDCard, + "name": paramsDto.Name, + "idCard": paramsDto.IDCard, } respBytes, err := deps.HuiboService.CallAPI2(ctx, "P_004_0271", reqdata) diff --git a/internal/infrastructure/external/huibo/2.md b/internal/infrastructure/external/huibo/2.md deleted file mode 100644 index 507e5fd..0000000 --- a/internal/infrastructure/external/huibo/2.md +++ /dev/null @@ -1,90 +0,0 @@ -自然人公开涉诉信息查询接口文档 -接口编码:BHSC-P_004_0271 -版本:V1.0 -实施日期:2026-04-13 -适用场景:合作厂家接入中胜信用平台,用于个人涉诉案件查询(含失信、限高) -1. 通信说明 -请求方式:HTTP POST -数据格式:JSON -编码格式:UTF-8 -安全要求:请求 IP 需提前绑定 -2. 请求信息 -请求地址 -http://host:port/api/data -请求头(Header) -表格 -参数名 含义 必填 类型 备注 -AppCode 接口授权码 Y String 接口服务商提供 -pcode 产品编码 Y String 固定值:P_004_0271 -请求体(Body) -表格 -参数名 含义 必填 类型 说明 -name 姓名 Y String 支持明文 / MD5 加密 -idCard 身份证号 Y String 支持明文 / MD5 加密 -请求示例(明文) -json -{ - "idCard": "2103111*****0", - "name": "*****" -} -请求示例(加密) -json -{ - "name": "MD5:3e29aae20b7b92775*****", - "idCard": "MD5:a0c28f3a5a*****14" -} -3. 响应信息 -表格 -字段名 含义 类型 备注 -code 状态码 String 参考状态码说明 -message 描述信息 String - -orderNo 订单号 String - -pcode 产品编码 String 与请求一致 -param 请求参数 Object 原样返回 -charge 是否收费 Boolean true = 收费;false = 不收费 -time 响应时间戳 String 13 位毫秒 -data 业务数据 Object 涉诉 / 失信 / 限高数据 -响应示例 -json -{ - "code": "100", - "orderNo": "1361269246899077120", - "charge": true, - "data": { - "ss": { - "preservation": { "count": {} }, - "crc": 35****4186, - "cases_tree": {}, - "administrative": {}, - "civil": {}, - "count": {}, - "implement": {}, - "criminal": {}, - "bankrupt": {} - }, - "sxbzxr": [{}], - "xgbzxr": [] - }, - "pcode": "P_004_0271", - "param": null, - "time": "1744593480538", - "message": "查询成功" -} -data 节点说明 -ss:涉诉案件(民事 / 刑事 / 执行 / 行政 / 破产等) -sxbzxr:失信被执行人 -xgbzxr:限制高消费 -4. 状态码说明 -表格 -状态码 描述 -100 查询成功 -101 参数错误 -103 账户不存在 -104 IP 限制 -105 账号已过期 -107 服务不存在 -108 产品通道已关闭 -109 账户资金不足 -110 查询成功,无数据 -500 未知请求错误 -要不要我帮你把这份接口直接写成可上线的小程序请求代码(含加密、header、异常捕获、状态码统一处理)? \ No newline at end of file diff --git a/internal/infrastructure/external/huibo/crypto_test.go b/internal/infrastructure/external/huibo/crypto_test.go new file mode 100644 index 0000000..6658db7 --- /dev/null +++ b/internal/infrastructure/external/huibo/crypto_test.go @@ -0,0 +1,432 @@ +package huibo + +import ( + "encoding/base64" + "testing" +) + +// 测试 MD5 加密(固定密文对比验证) +func TestMD5Encrypt(t *testing.T) { + appKey := "a6c9935e967894e731c62ecfcd9b7c95" + + testCases := []struct { + name string + data string + expected string // 固定密文 + }{ + { + name: "姓名", + data: "何志勇", + expected: "64d4d5c6457026117a4911acf189e269", // 固定密文 + }, + { + name: "身份证号", + data: "452528197907133014", + expected: "7c6cc77dabb83d95948904dba5a7219d", // 固定密文 + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // 加密 + result := MD5Encrypt(tc.data, appKey) + + // 最核心:对比是否和固定密文一致 + if result != tc.expected { + t.Errorf("加密不匹配!\n明文:%s\n期望密文:%s\n实际密文:%s", + tc.data, tc.expected, result) + return + } + + // 打印成功日志 + t.Logf("✅ 校验成功\n明文:%s\n密文:%s", tc.data, result) + }) + } +} + +// 测试 HMAC-SHA256 签名 +func TestHMACSHA256Base64(t *testing.T) { + secret := "test_secret_key" + + testCases := []struct { + name string + data string + secret string + }{ + { + name: "简单字符串", + data: "hello world", + secret: secret, + }, + { + name: "JSON数据", + data: `{"name":"张三","idCard":"110101199003072345"}`, + secret: secret, + }, + { + name: "URL参数", + data: "idCard=110101199003072345&name=张三&productCode=22089", + secret: secret, + }, + { + name: "空字符串", + data: "", + secret: secret, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := HMACSHA256Base64(tc.data, tc.secret) + + // 验证结果不为空 + if result == "" { + t.Error("HMAC-SHA256签名结果为空") + } + + // 验证结果是有效的Base64 + _, err := base64.StdEncoding.DecodeString(result) + if err != nil { + t.Errorf("HMAC-SHA256结果不是有效的Base64: %v", err) + } + + // 验证相同输入产生相同输出 + result2 := HMACSHA256Base64(tc.data, tc.secret) + if result != result2 { + t.Error("相同输入产生的签名不一致") + } + + // 验证不同输入产生不同输出 + result3 := HMACSHA256Base64(tc.data+"x", tc.secret) + if result == result3 { + t.Error("不同输入产生的签名相同") + } + + t.Logf("数据: %s, 签名: %s", tc.data, result) + }) + } +} + +// 测试 AES-GCM 加密解密 +func TestEncryptDecryptAESGCMBase64(t *testing.T) { + // 生成一个有效的 Base64 编密的 AES 密钥 + key := make([]byte, 32) // AES-256 + for i := range key { + key[i] = byte(i) + } + base64Key := base64.StdEncoding.EncodeToString(key) + + testCases := []struct { + name string + data string + }{ + { + name: "简单文本", + data: "hello world", + }, + { + name: "中文文本", + data: "你好世界", + }, + { + name: "JSON数据", + data: `{"name":"张三","idCard":"110101199003072345"}`, + }, + { + name: "长文本", + data: "这是一个很长的文本,用来测试加密解密功能是否正常工作。包含各种字符:123456789!@#$%^&*()_+-=[]{}|;':\",./<>?", + }, + { + name: "空字符串", + data: "", + }, + { + name: "特殊字符", + data: "\n\t\r\x00", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // 加密 + encrypted, err := EncryptAESGCMBase64(tc.data, base64Key) + if err != nil { + t.Fatalf("加密失败: %v", err) + } + + // 验证加密结果不为空 + if encrypted == "" { + t.Error("加密结果为空") + } + + // 验证加密结果是有效的Base64 + _, err = base64.StdEncoding.DecodeString(encrypted) + if err != nil { + t.Errorf("加密结果不是有效的Base64: %v", err) + } + + // 验证加密结果与原文不同 + if encrypted == tc.data { + t.Error("加密结果与原文相同") + } + + // 解密 + decrypted, err := DecryptAESGCMBase64(encrypted, base64Key) + if err != nil { + t.Fatalf("解密失败: %v", err) + } + + // 验证解密结果与原文一致 + if decrypted != tc.data { + t.Errorf("解密结果不匹配,期望: %s, 实际: %s", tc.data, decrypted) + } + + t.Logf("原文: %s", tc.data) + t.Logf("密文: %s", encrypted) + t.Logf("解密: %s", decrypted) + }) + } +} + +// 测试错误的密钥 +func TestEncryptDecryptWithWrongKey(t *testing.T) { + // 生成两个不同的密钥 + key1 := make([]byte, 32) + for i := range key1 { + key1[i] = byte(i) + } + base64Key1 := base64.StdEncoding.EncodeToString(key1) + + key2 := make([]byte, 32) + for i := range key2 { + key2[i] = byte(i + 1) + } + base64Key2 := base64.StdEncoding.EncodeToString(key2) + + data := "sensitive data" + + // 用密钥1加密 + encrypted, err := EncryptAESGCMBase64(data, base64Key1) + if err != nil { + t.Fatalf("加密失败: %v", err) + } + + // 用密钥2解密 + decrypted, err := DecryptAESGCMBase64(encrypted, base64Key2) + if err == nil { + t.Error("用错误密钥解密应该返回错误") + } + + // 验证解密结果与原文不同(如果解密成功的话) + if decrypted == data { + t.Error("用错误密钥解密不应该得到正确结果") + } + + t.Logf("用错误密钥解密预期失败: %v", err) +} + +// 测试无效的 Base64 密钥 +func TestEncryptWithInvalidBase64Key(t *testing.T) { + data := "test data" + + invalidKeys := []string{ + "", // 空字符串 + "not_base64", // 非Base64 + "abc", // 解码后太短 + } + + for _, invalidKey := range invalidKeys { + _, err := EncryptAESGCMBase64(data, invalidKey) + if err == nil { + t.Errorf("使用无效密钥 %s 应该返回错误", invalidKey) + } + t.Logf("无效密钥 %s 预期失败: %v", invalidKey, err) + } +} + +// 测试解密无效数据 +func TestDecryptWithInvalidData(t *testing.T) { + // 生成一个有效的密钥 + key := make([]byte, 32) + for i := range key { + key[i] = byte(i) + } + base64Key := base64.StdEncoding.EncodeToString(key) + + invalidData := []string{ + "", // 空字符串 + "invalid_base64", // 非Base64 + "dGVzdA==", // 有效的Base64但不是AES-GCM数据 + "short", // 太短 + } + + for _, data := range invalidData { + _, err := DecryptAESGCMBase64(data, base64Key) + if err == nil { + t.Errorf("使用无效数据 %s 应该返回错误", data) + } + t.Logf("无效数据 %s 预期失败: %v", data, err) + } +} + +// 测试加密结果的唯一性 +func TestEncryptionUniqueness(t *testing.T) { + // 生成一个有效的密钥 + key := make([]byte, 32) + for i := range key { + key[i] = byte(i) + } + base64Key := base64.StdEncoding.EncodeToString(key) + + data := "same data" + + // 加密多次 + results := make([]string, 10) + for i := 0; i < 10; i++ { + encrypted, err := EncryptAESGCMBase64(data, base64Key) + if err != nil { + t.Fatalf("第%d次加密失败: %v", i, err) + } + results[i] = encrypted + } + + // 验证每次加密结果都不同(因为包含随机IV) + uniqueCount := 0 + for i := 0; i < len(results); i++ { + isUnique := true + for j := 0; j < len(results); j++ { + if i != j && results[i] == results[j] { + isUnique = false + break + } + } + if isUnique { + uniqueCount++ + } + } + + if uniqueCount != len(results) { + t.Errorf("加密结果应该唯一,实际上只有%d个唯一结果,期望%d个", uniqueCount, len(results)) + } + + t.Logf("生成了%d个不同的加密结果", uniqueCount) +} + +// 测试使用真实配置的加密解密 +func TestEncryptionWithRealConfig(t *testing.T) { + // 使用配置文件中的真实AES密钥 + aesKey := "NQYN3YO+pb/GEcCBNX0ptMb7cUlnXSPvcX7VvNofBkc=" + appKey := "a6c9935e967894e731c62ecfcd9b7c95" + + // 测试数据 + testData := `{"name":"张三","idCard":"110101199003072345","productCode":"22089"}` + + t.Run("MD5加密", func(t *testing.T) { + // 测试 MD5 加密 + md5Result := MD5Encrypt("张三", appKey) + t.Logf("姓名 MD5: %s", md5Result) + + md5Result2 := MD5Encrypt("110101199003072345", appKey) + t.Logf("身份证号 MD5: %s", md5Result2) + + // 验证格式 + if len(md5Result) != 32 { + t.Errorf("MD5结果长度错误,期望32位,实际%d位", len(md5Result)) + } + }) + + t.Run("HMAC-SHA256签名", func(t *testing.T) { + // 生成排序后的参数 + sortedParam := "idCard=110101199003072345&name=张三&productCode=22089" + signature := HMACSHA256Base64(sortedParam, aesKey) + t.Logf("签名参数: %s", sortedParam) + t.Logf("HMAC-SHA256签名: %s", signature) + + // 验证格式 + _, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + t.Errorf("签名不是有效的Base64: %v", err) + } + }) + + t.Run("AES-GCM加密解密", func(t *testing.T) { + // 测试 AES-GCM 加密 + encrypted, err := EncryptAESGCMBase64(testData, aesKey) + if err != nil { + t.Fatalf("加密失败: %v", err) + } + t.Logf("原始数据: %s", testData) + t.Logf("加密结果: %s", encrypted) + + // 测试解密 + decrypted, err := DecryptAESGCMBase64(encrypted, aesKey) + if err != nil { + t.Fatalf("解密失败: %v", err) + } + t.Logf("解密结果: %s", decrypted) + + // 验证结果 + if decrypted != testData { + t.Errorf("解密结果不匹配") + } + }) +} + +// 基准测试 +func BenchmarkMD5Encrypt(b *testing.B) { + data := "张三" + appKey := "a6c9935e967894e731c62ecfcd9b7c95" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + MD5Encrypt(data, appKey) + } +} + +func BenchmarkHMACSHA256Base64(b *testing.B) { + data := "idCard=110101199003072345&name=张三" + secret := "a6c9935e967894e731c62ecfcd9b7c95" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + HMACSHA256Base64(data, secret) + } +} + +func BenchmarkEncryptAESGCMBase64(b *testing.B) { + data := `{"name":"张三","idCard":"110101199003072345","productCode":"22089"}` + + // 生成密钥 + key := make([]byte, 32) + for i := range key { + key[i] = byte(i) + } + base64Key := base64.StdEncoding.EncodeToString(key) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + EncryptAESGCMBase64(data, base64Key) + } +} + +func BenchmarkDecryptAESGCMBase64(b *testing.B) { + data := `{"name":"张三","idCard":"110101199003072345","productCode":"22089"}` + + // 生成密钥 + key := make([]byte, 32) + for i := range key { + key[i] = byte(i) + } + base64Key := base64.StdEncoding.EncodeToString(key) + + // 先加密一次 + encrypted, err := EncryptAESGCMBase64(data, base64Key) + if err != nil { + b.Fatalf("预加密失败: %v", err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + DecryptAESGCMBase64(encrypted, base64Key) + } +}