私钥错误修复说明.md 5.7 KB

私钥签名错误修复说明

问题描述

当使用自定义私钥调用签名接口时,出现以下错误:

{
  "error": "签名失败: The multiplicator cannot be negative",
  "success": false
}

###错误示例

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行:

// 错误的代码
BigInteger userD = new BigInteger(privateKey);

当使用 new BigInteger(byte[]) 构造器时:

  • 如果字节数组的第一个字节 >= 128(即最高位为1)
  • 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行:

修改前:

BigInteger userD = new BigInteger(privateKey);

修改后:

BigInteger userD = new BigInteger(1, privateKey);  // 1 表示正数符号位

完整修复步骤

步骤1: 修改代码

代码已自动修复(参考上述修改)。

步骤2: 重新编译

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镜像

# 停止旧容器
docker-compose down

# 重新构建镜像
docker-compose build --no-cache

# 启动新容器
docker-compose up -d

或使用一键脚本:

./deploy.sh

验证修复

生成有效的SM2密钥对

java -cp "bin:lib/*" demo.com.GenerateKeyPair

输出示例:

私钥 (64位十六进制):
FAA43158512CCA4DEBCA3047F4BD6F9E3BF1C10685E5FFB80B34B48DA854DCC1

公钥 (130位十六进制):
049DC52B5FFB819C19DB9FA4DC1AF97B754810E9EBCFF85D83BDFAB452C5DD53E8...

测试签名功能

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"
  }'

期望结果(修复后):

{
  "success": true,
  "signData": "TEST=hello|VERSION=1.0",
  "sign": "3044022015A7C08F039EAFAD3330D9B4456BD21B...",
  "timestamp": 1761292521531
}

为什么会出现这个问题?

Java BigInteger 的构造器说明

Java的 BigInteger 类有多个构造器:

// 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

使用方法

# 生成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