SM2 签名验证深度解析一、SM2 算法基础1.1 椭圆曲线参数SM2 使用 256 位素数域椭圆曲线标准参数GM/T 0003-2012p 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公钥PP d × G1.3 SM2 与标准 ECDSA 的区别特性标准 ECDSASM2签名前处理直接对 hash 签名需要 Z 值预处理哈希算法SHA-1/SHA-256SM3用户标识无有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)其中ENTLID 的比特长度用 2 字节表示ID用户可区分标识符默认值1234567812345678a, 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 或自定义 IDZ 值不同导致验证失败即使签名本身正确。项目经验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. 生成随机数 k1 ≤ k ≤ n-1 4. 计算 (x1, y1) k × G 5. 计算 r (e x1) mod n ← 若 r0 或 rkn 则重新生成 k 6. 计算 s (1d)^(-1) × (k - r×d) mod n ← 若 s0 则重新生成 7. 输出 (r, s)3.2 Z 值在签名中的作用注意第 2 步e SM3(Z ‖ M)。这里的关键是不是直接对 M 做 SM3而是对Z ‖ M做 SM3Z 在前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 bInternalHash1 requires SM3(Z ∥ raw data) verification当bInternalHash1时验证方必须知道固件使用的 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 ← 若 t0 则验证失败 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 (rs)×d×G (s rd sd)×G / (1d) × (1d) (s(1d) rd) × G / (1d) (s sd rd) × G / (1d)由签名关系s (k - rd)/(1d)得s(1d) k - rd即k s(1d) rds×G t×P k×G (x1, y1)因此x1 x1R (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’ eZ 值计算一致这是为什么 Z 值一致性如此重要——只要 Z 不同e 就不同验证必然失败。五、verify_with_sm3 与 verify 方法差异5.1 两种验证方法在 PolarSSL/mbedTLS 的 SM2 实现中通常有两个验证函数// 方法1标准 SM2 验证含 Z 预处理intsm2_verify_with_sm3(constunsignedchar*ID,size_tID_len,constunsignedchar*hash,// 原始消息非哈希size_thash_len,constunsignedchar*sig,size_tsig_len,constunsignedchar*Q,size_tQ_len// 公钥);// 方法2非标准验证无 Z 预处理intsm2_verify(constunsignedchar*hash,// 已计算好的哈希size_thash_len,constunsignedchar*sig,size_tsig_len,constunsignedchar*Q,size_tQ_len);5.2 关键差异对比特性verify_with_sm3verifyZ 值计算✅ 内部计算 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_SIGN0x00000001SM2 签名原始数据CKM_SM2_SIGN_VERIFY0x00010002SM2 签名验证CKM_SM2_SM3_SIGN_VERIFY0x00010004SM2SM3 签名验证CKM_SM30x00000020SM3 摘要6.2 项目中的机制选择逻辑当ulAlgID0时根据输入数据长度自动选择机制if(dataLen32){// 32字节 已计算好的SM3哈希// 使用 CKM_SM2_SIGN_VERIFY固件内部做 Z‖e 再签名mechanismCKM_SM2_SIGN_VERIFY;// 0x10002}else{// 原始数据// 使用 CKM_SM2_SIGN固件内部完成 SM3(Z‖M) 再签名mechanismCKM_SM2_SIGN;}6.3 交易签名的特殊路径交易签名需要确认bCfm1走特殊路径涉及 XML 解析[PARTNER_CODE] 标准和用户二次确认。非交易场景bCfm0必须走普通签名路径APDU P20x81否则 Key 返回 0x6982安全状态不满足。七、Key 类型与 Z 值处理7.1 CKK_SM2_WIN vs CKK_SM2CKK_SM2_WIN作为 SM2 私钥类型标记非标准CKK_SM2查找私钥时必须过滤此类型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字符串标记。# 检查动态库是否包含 Z 预处理逻辑strings[VENDOR_LIB_SM2].dylib|grepZZZZZZZZ八、硬件 Token 类型差异8.1 CToken3003 vs CTokeni3k[PARTNER_CODE]Token 类适用设备协议特点CToken3003标准 FT [DEVICE_MODEL_B]标准 [VENDOR] 协议CTokeni3k[PARTNER_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长度错误输入数据长度不正确十、工程最佳实践调试技巧打印 Z 值在签名和验证两端分别打印 Z确认一致打印 e 值确认SM3(Z‖M)结果相同二进制检查strings libxxx.dylib | grep ZZZZZZZZ确认 Z 处理逻辑抓包对比对比正常与异常环境的 APDU 报文参考文献GM/T 0003-2012 《SM2 椭圆曲线公钥密码算法》GM/T 0004-2012 《SM3 密码杂凑算法》PKCS#11 v2.20 规范