SM2签名验证深度解析

SM2 签名验证深度解析

一、SM2 算法基础

1.1 椭圆曲线参数

SM2 使用 256 位素数域椭圆曲线,标准参数(GM/T 0003-2012):

复制代码
p = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
a = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
b = 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
n = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
Gx = 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF 2660BE17 155F1A4F 0E50C1C2
Gy = 0E54E5B4 8906E88E 3D9DE9DB 58AB5B6B 8026CD29 D6D5D6C0 8B7B29C2 6F984FE4

基点 G = (Gx, Gy),阶为 n。

1.2 密钥对生成

  • 私钥 d:随机选取 1 ≤ d ≤ n-2
  • 公钥 P:P = d × G

1.3 SM2 与标准 ECDSA 的区别

特性 标准 ECDSA SM2
签名前处理 直接对 hash 签名 需要 Z 值预处理
哈希算法 SHA-1/SHA-256 SM3
用户标识 无 有(IDA)
预处理 无 Z = SM3(ENTL ‖ ID ‖ a ‖ b ‖ Gx ‖ Gy ‖ Px ‖ Py)

关键差异 :SM2 签名不是直接对原始数据或哈希签名,而是先计算 Z 值 (包含用户可区分标识符和公钥),再对 Z ‖ 原始数据 做 SM3 摘要后签名。这是 SM2 与 ECDSA 在工程实现上最大的不同。

二、Z 值计算详解

2.1 Z 值的定义

Z 值是 SM2 签名验证双方的"身份指纹",由以下信息拼接后做 SM3 摘要得到:

复制代码
Z = SM3(ENTL ‖ ID ‖ a ‖ b ‖ Gx ‖ Gy ‖ Px ‖ Py)

其中:

  • ENTL:ID 的比特长度,用 2 字节表示
  • ID :用户可区分标识符(默认值 1234567812345678)
  • a, b:曲线参数
  • Gx, Gy:基点坐标
  • Px, Py:公钥坐标

2.2 Z 值计算流程

复制代码
┌─────────────────────────────────────────────┐
│  1. ENTL = ID长度(字节) × 8,用2字节表示      │
│  2. 拼接: ENTL ‖ ID ‖ a ‖ b ‖ Gx ‖ Gy ‖ Px ‖ Py │
│  3. Z = SM3(上述拼接数据)                    │
└─────────────────────────────────────────────┘

2.3 默认 ID 的重要性

GM/T 0003 标准规定默认用户 ID 为:

复制代码
"1234567812345678"  (16字节ASCII)

对应的 ENTL = 16 × 8 = 128 = 0x0080。

工程陷阱:签名方与验证方必须使用相同的 ID。如果签名时用默认 ID,验证时却用了空 ID 或自定义 ID,Z 值不同,导致验证失败(即使签名本身正确)。

项目经验:

SM2 signature verification requires matching Z value calculation (user ID must be consistent)

三、签名生成流程

3.1 完整流程

SM2 签名生成算法(GM/T 0003 第2部分):

复制代码
输入:私钥 d、用户 ID、消息 M
输出:签名 (r, s)

1. 计算 Z = SM3(ENTL ‖ ID ‖ a ‖ b ‖ Gx ‖ Gy ‖ Px ‖ Py)
2. 计算 e = SM3(Z ‖ M)           ← 关键预处理
3. 生成随机数 k,1 ≤ k ≤ n-1
4. 计算 (x1, y1) = k × G
5. 计算 r = (e + x1) mod n        ← 若 r=0 或 r+k=n 则重新生成 k
6. 计算 s = (1+d)^(-1) × (k - r×d) mod n   ← 若 s=0 则重新生成
7. 输出 (r, s)

3.2 Z 值在签名中的作用

注意第 2 步:e = SM3(Z ‖ M)。这里的关键是:

  • 不是直接对 M 做 SM3
  • 而是 对 Z ‖ M 做 SM3(Z 在前,M 在后)

这意味着签名者必须知道自己的公钥(P = d × G),才能计算 Z。在 USBKey 中,公钥存储在卡内,签名时由固件完成 Z 计算和签名。

3.3 bInternalHash 参数

VENDOR SKF 接口(SKF_ECCSignData)有 bInternalHash 参数:

  • bInternalHash = 1:固件内部完成 SM3(Z ‖ rawdata) 摘要再签名
  • bInternalHash = 0:调用方传入已计算好的哈希,固件直接签名

项目经验:

SKF_ECCSignData with bInternalHash=1 requires SM3(Z ∥ raw data) verification

当 bInternalHash=1 时,验证方必须知道固件使用的 Z 值,自行计算 SM3(Z ‖ rawdata) 得到 e,才能验证签名。

四、签名验证流程

3.1 完整流程

SM2 签名验证算法:

复制代码
输入:公钥 P、用户 ID、消息 M、签名 (r, s)
输出:验证通过/失败

1. 检查 r、s 范围:1 ≤ r,s ≤ n-1
2. 计算 Z = SM3(ENTL ‖ ID ‖ a ‖ b ‖ Gx ‖ Gy ‖ Px ‖ Py)
3. 计算 e = SM3(Z ‖ M)           ← 必须与签名方相同
4. 计算 t = (r + s) mod n        ← 若 t=0 则验证失败
5. 计算 (x1, y1) = s×G + t×P
6. 计算 R = (e + x1) mod n
7. 比较 R == r,相等则验证通过

4.2 数学等价性证明

签名时有 r = (e + x1) mod n,其中 (x1,y1) = k×G。

验证时计算 (x1',y1') = s×G + t×P,展开:

复制代码
s×G + t×P = s×G + (r+s)×d×G
          = (s + rd + sd)×G / (1+d) × (1+d)
          = (s(1+d) + rd) × G / (1+d)
          = (s + sd + rd) × G / (1+d)

由签名关系 s = (k - rd)/(1+d),得 s(1+d) = k - rd,即 k = s(1+d) + rd:

复制代码
s×G + t×P = k×G = (x1, y1)

因此 x1' = x1,R = (e + x1') mod n = (e + x1) mod n = r,验证通过。

4.3 e' 必须等于 e 的关键

验证算法第 7 步比较的是 R = (e + x1) mod n 与 r。而签名时 r = (e + x1) mod n,所以:

验证成功的充要条件是 e' == e(Z 值计算一致)

这是为什么 Z 值一致性如此重要------只要 Z 不同,e 就不同,验证必然失败。

五、verify_with_sm3 与 verify 方法差异

5.1 两种验证方法

在 PolarSSL/mbedTLS 的 SM2 实现中,通常有两个验证函数:

c 复制代码
// 方法1:标准 SM2 验证(含 Z 预处理)
int sm2_verify_with_sm3(
    const unsigned char *ID, size_t ID_len,
    const unsigned char *hash,        // 原始消息(非哈希)
    size_t hash_len,
    const unsigned char *sig, size_t sig_len,
    const unsigned char *Q, size_t Q_len  // 公钥
);

// 方法2:非标准验证(无 Z 预处理)
int sm2_verify(
    const unsigned char *hash,        // 已计算好的哈希
    size_t hash_len,
    const unsigned char *sig, size_t sig_len,
    const unsigned char *Q, size_t Q_len
);

5.2 关键差异对比

特性 verify_with_sm3 verify
Z 值计算 ✅ 内部计算 Z ❌ 不计算 Z
输入数据 原始消息 M 已计算的哈希 e
摘要计算 内部完成 SM3(Z‖M) 不做(调用方负责)
适用场景 标准 SM2 签名验证 非标准/自定义预处理
一致性保证 签名验证双方 Z 必须一致 调用方自行保证 e 一致

5.3 项目中的选择

项目经验:

测试_副本.sh 使用旧版 dylib 无 Z 预处理(无 'ZZZZZZZZ' 字符串,32-byte SM3 hash)

旧版动态库不含 Z 预处理逻辑(搜索二进制无 ZZZZZZZZ 字符串),直接对 32 字节 SM3 哈希签名。新版动态库增加了 Z 预处理,符合标准。

复制代码
旧版流程:  M → SM3 → e(32字节) → 签名            (非标准)
新版流程:  M → Z‖M → SM3 → e(32字节) → 签名      (标准)

对应验证方也要选择匹配的方法:

  • 旧版签名 → 用 verify(直接传 32 字节哈希)
  • 新版签名 → 用 verify_with_sm3(传原始消息,内部计算 Z)

六、PKCS#11 机制选择策略

6.1 三种 SM2 签名机制

PKCS#11 定义了多个 SM2 相关机制:

机制 值 含义
CKM_SM2_SIGN 0x00000001 SM2 签名(原始数据)
CKM_SM2_SIGN_VERIFY 0x00010002 SM2 签名验证
CKM_SM2_SM3_SIGN_VERIFY 0x00010004 SM2+SM3 签名验证
CKM_SM3 0x00000020 SM3 摘要

6.2 项目中的机制选择逻辑

当 ulAlgID=0 时,根据输入数据长度自动选择机制:

c 复制代码
if (dataLen == 32) {
    // 32字节 = 已计算好的SM3哈希
    // 使用 CKM_SM2_SIGN_VERIFY,固件内部做 Z‖e 再签名
    mechanism = CKM_SM2_SIGN_VERIFY;  // 0x10002
} else {
    // 原始数据
    // 使用 CKM_SM2_SIGN,固件内部完成 SM3(Z‖M) 再签名
    mechanism = CKM_SM2_SIGN;
}

6.3 交易签名的特殊路径

交易签名(需要确认,bCfm=1)走特殊路径,涉及 XML 解析(PARTNER_CODE 标准)和用户二次确认。非交易场景(bCfm=0)必须走普通签名路径(APDU P2=0x81),否则 Key 返回 0x6982(安全状态不满足)。

七、Key 类型与 Z 值处理

7.1 CKK_SM2_WIN vs CKK_SM2

CKK_SM2_WIN 作为 SM2 私钥类型标记(非标准 CKK_SM2),查找私钥时必须过滤此类型:

c 复制代码
CK_ATTRIBUTE keyTemplate[] = {
    { CKA_CLASS,       &privKeyClass, sizeof(privKeyClass) },
    { CKA_KEY_TYPE,    &sm2WinType,   sizeof(sm2WinType) },  // CKK_SM2_WIN
    { CKA_ID,          keyId,         keyIdLen }
};

CKK_SM2_WIN 类型的密钥签名时,Key 固件期望输入为 Z ‖ raw data 形式(Z 在前),由固件内部完成 SM3 摘要。如果在 session.cpp 中不添加 Z 值前缀,Key 返回 0x6985(使用条件不满足)。

7.2 Z 值预处理库的安装

Z 值预处理逻辑包含在特定版本的动态库中。验证方法:搜索库二进制是否包含 ZZZZZZZZ 字符串标记。

bash 复制代码
# 检查动态库是否包含 Z 预处理逻辑
strings [VENDOR_LIB_SM2].dylib | grep "ZZZZZZZZ"

八、硬件 Token 类型差异

8.1 CToken3003 vs CTokeni3kPARTNER_CODE

Token 类 适用设备 协议特点
CToken3003 标准 FT DEVICE_MODEL_B 标准 VENDOR 协议
CTokeni3kPARTNER_CODE PARTNER_NAME(PARTNER_NAME_CN)系列 PARTNER_CODE 自定义协议

日志中的 ATR 比对(compare Atr)就是用来区分设备类型,从而选择正确的 Token 处理类。

8.2 APDU 命令差异

读取公钥的 APDU 命令:

复制代码
80 E6 19 1F 40

不同 Token 类型的 APDU 可能不同,这解释了为什么驱动库的公钥命令会变化------硬件版本不同使用不同协议。

九、常见错误码速查

错误码 含义 可能原因
0x6982 安全状态不满足 未走正确的签名路径(P2值错误)
0x6985 使用条件不满足 Z 值未预处理 / Key 类型不匹配
0x6A80 数据参数错误 数据长度/格式不符
0x6700 长度错误 输入数据长度不正确

十、工程最佳实践

调试技巧

  1. 打印 Z 值:在签名和验证两端分别打印 Z,确认一致
  2. 打印 e 值 :确认 SM3(Z‖M) 结果相同
  3. 二进制检查 :strings libxxx.dylib | grep ZZZZZZZZ 确认 Z 处理逻辑
  4. 抓包对比:对比正常与异常环境的 APDU 报文

参考文献:

  • GM/T 0003-2012 《SM2 椭圆曲线公钥密码算法》
  • GM/T 0004-2012 《SM3 密码杂凑算法》
  • PKCS#11 v2.20 规范
相关推荐
padane223 小时前
GmSSL 3.1.1 跨平台静态库编译
国密·gmssl
幻智星科技1 天前
Karatsuba与CSD优化FPGA大数乘法
c++·fpga开发·密码学
安当加密-焱垚2 天前
静态脱敏系统解决方案
国密·数据脱敏·静态脱敏系统解决方案·静态脱敏系统访问控制·静态脱敏系统防勒索方案
安当加密-焱垚6 天前
轻量级证书签发系统如何集成
国密·轻量级证书签发系统如何集成·轻量级证书签发系统成本分析·轻量级证书签发系统技术趋势分析
安当加密-焱垚8 天前
统一身份认证系统常见问题解答
国密·统一身份认证系统常见问题解答·统一身份认证系统性能对比·统一身份认证系统技术趋势分析
deepseek2310 天前
AI 模型隐藏任意能力拆解:把加密神经网络塞进宿主权重,能力评估为什么在密码学上抓不到
人工智能·深度学习·神经网络·密码学·模型安全
安当加密-焱垚13 天前
静态脱敏系统身份认证方案
国密·静态脱敏系统身份认证方案·合规审计中如何应用静态脱敏系统·登录认证中如何应用静态脱敏系统·身份认证中如何应用静态脱敏系统
曼岛_13 天前
[图解密码学]第1章 密码学是什么
网络·密码学·图解密码学