# 私钥签名错误修复说明 ## 问题描述 当使用自定义私钥调用签名接口时,出现以下错误: ```json { "error": "签名失败: The multiplicator cannot be negative", "success": false } ``` ###错误示例 ```bash curl -X POST http://localhost:8888/api/sign \ -H "Content-Type: application/json" \ -d '{ "data": {"version": "1.0"}, "priKey": "AABBCCDD0011223344556677889900AABBCCDD0011223344556677889900AA", "reqOrgNo": "TEST123456" }' # 返回错误 {"error":"签名失败: The multiplicator cannot be negative","success":false} ``` --- ## 根本原因 问题出在 `SM2Utils.java` 第135行: ```java // 错误的代码 BigInteger userD = new BigInteger(privateKey); ``` 当使用 `new BigInteger(byte[])` 构造器时: - 如果字节数组的第一个字节 >= 128(即最高位为1) - Java会将其解释为**负数**(二进制补码表示) - 导致后续的椭圆曲线运算失败 ### 示例说明 ```java // 假设私钥第一个字节是 0xFA (250) byte[] key = Hex.decode("FAA43158512CCA4DEBCA3047F4BD6F9E..."); // 错误方式:会被解释为负数 BigInteger wrong = new BigInteger(key); // wrong < 0 ❌ // 正确方式:明确指定为正数 BigInteger correct = new BigInteger(1, key); // correct > 0 ✅ ``` --- ## 解决方案 ### 修复代码 在 `/Users/light/www/pros/xingfutong-java/src/demo/com/util/sm/SM2Utils.java` 第135行: **修改前**: ```java BigInteger userD = new BigInteger(privateKey); ``` **修改后**: ```java BigInteger userD = new BigInteger(1, privateKey); // 1 表示正数符号位 ``` ### 完整修复步骤 #### 步骤1: 修改代码 代码已自动修复(参考上述修改)。 #### 步骤2: 重新编译 ```bash cd /Users/light/www/pros/xingfutong-java # 编译SM2Utils javac -encoding UTF-8 -d bin -cp "lib/*:bin" \ src/demo/com/util/sm/SM2Utils.java ``` #### 步骤3: 重新构建Docker镜像 ```bash # 停止旧容器 docker-compose down # 重新构建镜像 docker-compose build --no-cache # 启动新容器 docker-compose up -d ``` 或使用一键脚本: ```bash ./deploy.sh ``` --- ## 验证修复 ### 生成有效的SM2密钥对 ```bash java -cp "bin:lib/*" demo.com.GenerateKeyPair ``` 输出示例: ``` 私钥 (64位十六进制): FAA43158512CCA4DEBCA3047F4BD6F9E3BF1C10685E5FFB80B34B48DA854DCC1 公钥 (130位十六进制): 049DC52B5FFB819C19DB9FA4DC1AF97B754810E9EBCFF85D83BDFAB452C5DD53E8... ``` ### 测试签名功能 ```bash curl -X POST http://localhost:8888/api/sign \ -H "Content-Type: application/json" \ -d '{ "data": { "version": "1.0", "test": "hello" }, "priKey": "FAA43158512CCA4DEBCA3047F4BD6F9E3BF1C10685E5FFB80B34B48DA854DCC1", "reqOrgNo": "MY_ORG" }' ``` **期望结果**(修复后): ```json { "success": true, "signData": "TEST=hello|VERSION=1.0", "sign": "3044022015A7C08F039EAFAD3330D9B4456BD21B...", "timestamp": 1761292521531 } ``` --- ## 为什么会出现这个问题? ### Java BigInteger 的构造器说明 Java的 `BigInteger` 类有多个构造器: ```java // 1. 从字节数组构造(有符号) new BigInteger(byte[] val) // - 使用二进制补码表示 // - 第一个字节的最高位是符号位 // - 如果最高位=1,则为负数 // 2. 从字节数组构造(指定符号) new BigInteger(int signum, byte[] magnitude) // - signum: -1负数, 0零, 1正数 // - magnitude: 绝对值的二进制表示 // - 总是按无符号解释字节数组 ``` ### SM2私钥的特点 SM2私钥是32字节(256位)的随机数: - 范围:1 到 n-1(n是椭圆曲线的阶) - 十六进制表示:64个字符 - **约50%的私钥第一个字节 >= 128** 因此,如果不指定符号位,大约一半的私钥会被错误解释为负数! --- ## 其他说明 ### 1. 默认私钥为什么能工作? 项目中的默认私钥: ``` 3164EE0DF2BCA7A12309383E3305DD6563A28DFE53F65BBD60B3A1D7F80AC275 ``` 第一个字节是 `0x31` (49),小于 128,所以被正确解释为正数。 ### 2. 哪些私钥会出错? 第一个字节 >= 128(80-FF)的私钥会出错,例如: - `FAA43158...` (0xFA = 250) - `AAB43158...` (0xAA = 170) - `80000000...` (0x80 = 128) 第一个字节 < 128(00-7F)的私钥正常,例如: - `31640000...` (0x31 = 49) - `7FFFFFFF...` (0x7F = 127) ### 3. 是否需要重新生成密钥? **不需要!** 修复后,所有有效的SM2私钥都能正常使用。 --- ## 生成密钥对工具 项目已包含密钥对生成工具 `GenerateKeyPair.java`。 ### 使用方法 ```bash # 生成1对密钥 java -cp "bin:lib/*" demo.com.GenerateKeyPair # 生成5对密钥 java -cp "bin:lib/*" demo.com.GenerateKeyPair 5 ``` ### 输出内容 - 私钥(64位十六进制) - 公钥(130位十六进制) - 配置文件格式 - API调用示例 --- ## 安全提醒 1. ⚠️ **私钥必须保密!** - 私钥泄露意味着签名被破解 - 不要将私钥提交到代码仓库 - 生产环境使用环境变量或密钥管理服务 2. ⚠️ **使用HTTPS!** - 如果通过API传递私钥,必须使用HTTPS - 防止中间人攻击窃取私钥 3. ⚠️ **定期轮换密钥!** - 定期更换密钥对 - 保留旧公钥用于验证历史签名 --- ## 总结 | 项目 | 说明 | |------|------| | **问题** | 部分私钥签名失败 | | **原因** | BigInteger构造器未指定正数符号位 | | **影响** | 约50%的随机私钥受影响 | | **修复** | 使用 `new BigInteger(1, privateKey)` | | **状态** | 已修复 ✅ | | **测试** | 需重新编译和部署 | --- ## 参考资料 - Java BigInteger API文档 - SM2国密标准 (GM/T 0003-2012) - BouncyCastle密码库文档 --- **最后更新**: 2025-10-24 **修复作者**: AI Assistant