文章总结: 本文详细拆解了智能密码钥匙进行ECC数字签名的19步标准流程,涵盖设备枚举连接锁定、应用容器操作、SM2预处理三步曲、PIN校验及签名执行等关键环节。每步均引用SKF标准函数原型与技术要点,强调严格按序调用与资源清理。文章为开发者提供了可直接参考的API调用指南,具有强实操性。 综合评分: 100 文章分类: 安全工具,应用安全,数据安全,网络安全,其他
把智能密码钥匙的 19 步签名流程,讲给你听
原创
利刃信安 利刃信安
利刃信安
2026年8月21日 13:30 北京
在小说阅读器读本章
去阅读
把智能密码钥匙的 19 步签名流程,讲给你听
今天不聊人生,不聊理想,咱们聊一个更硬核的东西——智能密码钥匙怎么给数据做 ECC 数字签名。
你可能会问:这玩意儿跟我有什么关系?
这么说吧——你用的网银 U 盾、政务系统的身份认证、电子合同的签署,背后都有它的身影。它就像你数字世界里的 私章,只不过这枚章刻在了一块小小的硬件里,谁也偷不走、复制不了。
01
一个签名,为什么需要 19 步?
你可能会想:签名不就是按一下按钮的事儿吗?
没错,对用户来说就一步。但对程序来说,这是一场 层层通关的密室逃脱。
你要先找到设备、连上设备、锁定设备、找到应用、打开应用、找到容器、打开容器、导出公钥、预处理(还要分三步)、校验口令、执行签名、清理状态、关闭句柄……整整 19 步。一步都不能少,一步都不能乱。
今天,我就带着大家把这 19 步从头到尾拆个底朝天,每一步都附上标准原文引用,保证你看完就能写出稳如老狗的调用代码。
02
第一关:找到你的钥匙
第 1 步:SKF_EnumDev —— 枚举设备
你掏出 U 盾插进电脑,程序怎么知道它存在?
获取设备名称列表:
SKF-Token-01
SKF_EnumDev 就是程序的眼睛——扫描当前系统里所有的智能密码钥匙,把它们的设备名称列表返回来。
标准依据:7.1.4 枚举设备
函数原型:ULONG DEVAPI SKF_EnumDev(BOOL bPresent, LPSTR szNameList, ULONG *pulSize)
技术要点:
– bPresent 为 TRUE 表示只取当前设备状态为存在的设备列表,为 FALSE 表示取当前驱动支持的设备列表。
-
先以 szNameList = NULL 调用一次,由 pulSize 返回所需缓冲区字节长度;再分配内存二次调用。
-
每个设备名称以单个 \0 结束,以双 \0 表示列表结束。
-
返回值 SAR_OK(0)表示成功。
第 2 步:SKF_ConnectDev —— 连接设备
从列表里选中你的那把钥匙,通过设备名称连接它,拿到一个设备句柄。
句柄是什么?你可以理解为 通行证——有了它,你才能跟设备对话。
标准依据:7.1.5 连接设备
函数原型:ULONG DEVAPI SKF_ConnectDev(LPSTR szName, DEVHANDLE *phDev)
技术要点:连接成功后 phDev 返回设备操作句柄,用于后续所有操作。
第 3 步:SKF_LockDev —— 锁定设备
设备是共享资源,万一你正签名呢,另一个程序跑来捣乱怎么办?
SKF_LockDev 给你一把 独占锁——锁定期间,只有你能访问设备,其他应用靠边站。
标准依据:7.1.10 锁定设备
函数原型:ULONG DEVAPI SKF_LockDev(DEVHANDLE hDev, ULONG ulTimeOut)
技术要点:
– ulTimeOut 为超时时间(毫秒),0xFFFFFFFF 表示无限等待。
- 调用后应尽快完成操作并及时解锁,避免长时间占用。
03
第二关:找到你要用的那个”账号”
第 4 步:SKF_EnumApplication —— 枚举应用
一个 U 盾里可以存多个应用——好比一张银行卡可以绑多个账户。
获取应用名列表:
DemoApp
这一步就是列出所有应用的名称。
标准依据:7.3.3 枚举应用
函数原型:ULONG DEVAPI SKF_EnumApplication(DEVHANDLE hDev, LPSTR szAppName, ULONG *pulSize)
技术要点:每个应用名称以单个 \0 结束,以双 \0 表示列表结束。
第 5 步:SKF_OpenApplication —— 打开应用
找到目标应用,打开它,拿到应用句柄。
标准依据:7.3.5 打开应用
函数原型:ULONG DEVAPI SKF_OpenApplication(DEVHANDLE hDev, LPSTR szAppName, HAPPLICATION *phApplication)
第 6 步:SKF_EnumContainer —— 枚举容器
应用下面是容器。每个容器里放着一对非对称密钥(公钥 + 私钥)。
获取容器名称列表:
container1
这一步列出所有容器的名字。
标准依据:7.5.6 枚举容器
函数原型:ULONG DEVAPI SKF_EnumContainer(HAPPLICATION hApplication, LPSTR szContainerName, ULONG *pulSize)
技术要点:每个容器名以单个 \0 结束,以双 \0 表示列表结束。
第 7 步:SKF_OpenContainer —— 打开容器
打开你要用的那个容器,拿到容器句柄。
这个句柄将作为私钥句柄传入后续的签名函数。
标准依据:7.5.4 打开容器
函数原型:ULONG DEVAPI SKF_OpenContainer(HAPPLICATION hApplication, LPSTR szContainerName, HCONTAINER *phContainer)
第 8 步:SKF_ExportPublicKey —— 导出签名公钥
把容器的签名公钥导出来。
获取签名公钥:
000001001e57b6ef3bfbd22792e1d6738c9f9db400b195ba0f2a6d8042c2439276458d5e0cf0a00507c2852d2f65e5366a33ff3906403770f18404e04ee4b9369b0a7b15
注意:这里存在瑕疵。
公钥正确格式应该是:
0001000000000000000000000000000000000000000000000000000000000000000000001e57b6ef3bfbd22792e1d6738c9f9db400b195ba0f2a6d8042c2439276458d5e00000000000000000000000000000000000000000000000000000000000000000cf0a00507c2852d2f65e5366a33ff3906403770f18404e04ee4b9369b0a7b15
解析后:
0001000000000000000000000000000000000000000000000000000000000000000000001e57b6ef3bfbd22792e1d6738c9f9db400b195ba0f2a6d8042c2439276458d5e00000000000000000000000000000000000000000000000000000000000000000cf0a00507c2852d2f65e5366a33ff3906403770f18404e04ee4b9369b0a7b15
公钥是用来给别人验签的,可以公开。私钥?永远锁在硬件里,谁也拿不出来。
标准依据:7.6.18 导出公钥
函数原型:ULONG DEVAPI SKF_ExportPublicKey(HCONTAINER hContainer, BOOL bSignFlag, BYTE *pbBlob, ULONG *pulBlobLen)
技术要点:
– bSignFlag 为 TRUE 表示导出签名公钥,为 FALSE 表示导出加密公钥。
– pbBlob 返回的数据结构视容器类型而定:RSA 容器为 RSAPUBLICKEYBLOB,ECC/SM2 容器为 ECCPUBLICKEYBLOB,SM9 容器为用户标识(BYTE 数组)。
04
第三关:准备要签名的”指纹”(SM2 预处理三步曲)
签名不是直接签原始数据——那样效率太低,国标 SM2 还规定了复杂的预处理流程。
第 9 步:SKF_DigestInit —— 初始化杂凑,并执行预处理 1
这一步指定用什么算法算”指纹”(预处理值)。
当算法为 SGD_SM3 且用户 ID 长度不为 0 时,需传入签名者的公钥和 ID 信息,设备内部会先计算预处理1值 ZA 并作为杂凑的初始输入,完成 SM2 签名预处理第 1 步。
标准依据:7.6.28 密码杂凑初始化
函数原型:ULONG DEVAPI SKF_DigestInit(DEVHANDLE hDev, ULONG ulAlgID, ECCPUBLICKEYBLOB *pPubKey, BYTE *pucID, ULONG ulIDLen, HANDLE *phHash)
技术要点:
- 标准原文注明:当 ulAlgID 为 SGD_SM3 且 ulIDLen 不为 0 时,pPubKey、pucID 有效,执行 SM2 算法签名预处理 1 操作(计算过程符合 GM/T 0009-2023 中 8.1 的规定)。
– phHash 返回密码杂凑对象句柄,后续步骤均需使用。
第 10 步:SKF_DigestUpdate —— 多包更新,执行预处理 2
原始消息可能很大,可以分多次喂给设备。每调用一次,设备就累积计算杂凑。
标准依据:7.6.30 多包数据密码杂凑
函数原型:ULONG DEVAPI SKF_DigestUpdate(HANDLE hHash, BYTE *pbData, ULONG ulDataLen)
技术要点:可重复调用,直到所有消息片段都传入。
第 11 步:SKF_DigestFinal —— 结束杂凑,完成预处理操作
所有数据喂完后,调用此函数结束计算,得到最终的预处理值(SM3 结果为 32 字节)。
获取预处理值:
1AB4C22891AE8F62AF329EE96B40C9850809E42395C0C7FA5BCEDEE799100022
标准依据:7.6.31 结束密码杂凑
函数原型:ULONG DEVAPI SKF_DigestFinal(HANDLE hHash, BYTE *pHashData, ULONG *pulHashLen)
技术要点:pHashData 就是待签名的预处理值,一定要妥善保存。
05
第四关:验明正身,执行签名
第 12 步:SKF_VerifyPIN —— 校验 PIN 码
这是最关键的安全关卡。你要输入正确的 PIN 码,证明 你是设备的合法主人。密码不对?对不起,后面的签名操作一概不给你做。
标准依据:7.2.6 校验 PIN
函数原型:ULONG DEVAPI SKF_VerifyPIN(HAPPLICATION hApplication, ULONG ulPINType, LPSTR szPIN, ULONG *pulRetryCount)
技术要点:
– ulPINType 为 ADMIN_TYPE(管理员,值 0)或 USER_TYPE(用户,值 1)。签名场景下通常校验 USER_TYPE 以取得用户权限。
- 校验成功后获得相应权限;若 PIN 错误,pulRetryCount 返回剩余重试次数,当剩余次数为 0 时表示 PIN 码已锁死。
第 13 步:SKF_ECCSignData —— ECC 数字签名(正主登场)
终于到正主了!
获取签名值:
bb51e92b9137350f6cd875290ad6755b5f4631a17c61fcb0f8e2789f8f6747f764db56f40ad9472a078426ae268c22aafbba8657ad9a89fe557365d3fefa7f4f
使用第 7 步得到的容器句柄,对 第 11 步计算出的预处理值 进行 ECC(如 SM2)数字签名,签名结果存入 pSignature。
标准依据:7.6.11 ECC 签名
函数原型:ULONG DEVAPI SKF_ECCSignData(HCONTAINER hContainer, BYTE *pbData, ULONG ulDataLen, PECCSIGNATUREBLOB pSignature)
技术要点:
– 输入数据为待签数据的杂凑值。当使用 SM2 算法时,该输入数据为待签数据经 SM2 签名预处理操作得到的结果(预处理过程符合 GM/T 0009-2023 第 8 章)。
– ulDataLen 为待签名数据长度,应小于密钥模长(SM2 下即小于 32 字节的模长边界,杂凑值本身为 32 字节)。
-
权限要求:已取得用户权限(即须先通过第 12 步 SKF_VerifyPIN)。
-
签名结果结构为 ECCSIGNATUREBLOB,由 r 和 s 两个 BYTE 数组组成(各 ECC_MAX_XCOORDINATE_BITS_LEN/8,即 64 字节)。
-
标准”引言”流程里写的”采用 ECC 算法和指定私钥 hKey”系原文笔误,实际签名函数使用的参数是容器句柄 hContainer,而非某个独立私钥句柄。
第 14 步:SKF_ClearSecureState —— 清除安全状态
签名做完了,赶紧把当前的安全状态清掉。标准原文的说法是”清除应用当前的安全状态,释放所获得的所有权限(包括用户权限和管理员权限)”。用完即焚,不留安全隐患。
标准依据:7.2.8 清除应用安全状态
函数原型:ULONG DEVAPI SKF_ClearSecureState(HAPPLICATION hApplication)
06
第五关:打扫战场,释放资源
第 15 步:SKF_CloseHandle —— 关闭杂凑对象句柄
关闭第 9 步创建的杂凑对象句柄 phHash(即 SKF_DigestInit 返回的句柄),释放相关资源。
标准依据:7.6.40 关闭密码对象句柄
函数原型:ULONG DEVAPI SKF_CloseHandle(HANDLE hHandle)
技术要点:此接口不限于杂凑对象,还可关闭会话密钥、消息鉴别码对象、ECC 密钥协商句柄等多种对象。
第 16 步:SKF_CloseContainer —— 关闭容器
关闭容器句柄,释放资源。
标准依据:7.5.5 关闭容器
函数原型:ULONG DEVAPI SKF_CloseContainer(HCONTAINER hContainer)
第 17 步:SKF_CloseApplication —— 关闭应用
关闭应用句柄。
标准依据:7.3.6 关闭应用
函数原型:ULONG DEVAPI SKF_CloseApplication(HAPPLICATION hApplication)
技术要点:此函数不影响应用安全状态。
第 18 步:SKF_UnlockDev —— 解锁设备
释放独占锁,允许其他程序访问设备。把”有人”的牌子摘下来。
标准依据:7.1.11 解锁设备
函数原型:ULONG DEVAPI SKF_UnlockDev(DEVHANDLE hDev)
第 19 步:SKF_DisConnectDev —— 断开连接
断开设备连接,释放设备句柄。至此,整个签名流程干干净净地结束。
标准依据:7.1.6 断开连接
函数原型:ULONG DEVAPI SKF_DisConnectDev(DEVHANDLE hDev)
技术要点:如果设备已被锁定,函数首先解锁该设备;断开连接操作并不影响设备的权限状态。
07
附加:完整调用伪代码(错误处理省略)
…c
DEVHANDLE hDev;
HAPPLICATION hApp;
HCONTAINER hContainer;
HANDLE hHash;
ECCPUBLICKEYBLOB pubKey;
ULONG ulSize = 0;
ULONG pubKeyLen = sizeof(pubKey);
BYTE hash[32]; ULONG hashLen = sizeof(hash);
BYTE userID[] = “1234567812345678”; /* 签名者 ID,SM2 预处理用 */
ECCSIGNATUREBLOB signature;
ULONG retryCount = 0;
LPSTR szDevName, szAppName, szContainerName;
/* 1. 枚举设备:先取长度,再分配并二次调用 */
SKF_EnumDev(TRUE, NULL, &ulSize);
szDevName = (LPSTR)malloc(ulSize);
SKF_EnumDev(TRUE, szDevName, &ulSize);
/* 2. 连接设备 */
SKF_ConnectDev(szDevName, &hDev);
/* 3. 锁定设备(无限等待) */
SKF_LockDev(hDev, 0xFFFFFFFF);
/* 4. 枚举应用 */
SKF_EnumApplication(hDev, NULL, &ulSize);
szAppName = (LPSTR)malloc(ulSize);
SKF_EnumApplication(hDev, szAppName, &ulSize);
/* 5. 打开应用 */
SKF_OpenApplication(hDev, “MyApp”, &hApp);
/* 6. 枚举容器 */
SKF_EnumContainer(hApp, NULL, &ulSize);
szContainerName = (LPSTR)malloc(ulSize);
SKF_EnumContainer(hApp, szContainerName, &ulSize);
/* 7. 打开容器 */
SKF_OpenContainer(hApp, “MyContainer”, &hContainer);
/* 8. 导出签名公钥 */
SKF_ExportPublicKey(hContainer, TRUE, (BYTE *)&pubKey, &pubKeyLen);
/* 9. 杂凑初始化(SM2 预处理1:计算 ZA 并初始化杂凑) */
SKF_DigestInit(hDev, SGD_SM3, &pubKey, userID, sizeof(userID) – 1, &hHash);
/* 10. 多包更新(预处理2,可重复调用) */
SKF_DigestUpdate(hHash, data1, len1);
SKF_DigestUpdate(hHash, data2, len2);
/* 11. 结束杂凑(得到最终的预处理值 e) */
SKF_DigestFinal(hHash, hash, &hashLen);
/* 12. 校验用户 PIN,取得用户权限 */
// 注意,这里仅演示过程,实际调用过程中口令不允许硬编码
SKF_VerifyPIN(hApp, USER_TYPE, “88888888”, &retryCount);
/* 13. ECC 签名(注意:pbData 传入的是预处理值 e!) */
SKF_ECCSignData(hContainer, hash, hashLen, &signature);
/* 14. 清除安全状态 */
SKF_ClearSecureState(hApp);
/* 15. 关闭杂凑句柄 */
SKF_CloseHandle(hHash);
/* 16. 关闭容器 */
SKF_CloseContainer(hContainer);
/* 17. 关闭应用 */
SKF_CloseApplication(hApp);
/* 18. 解锁设备 */
SKF_UnlockDev(hDev);
/* 19. 断开连接 */
SKF_DisConnectDev(hDev);
08
为什么要搞这么复杂?
你可能觉得 19 步太啰嗦了。
但你想过没有——数字世界的信任,恰恰建立在这些”啰嗦”之上。
每一步都是一道安全关卡:锁定设备防止冲突、校验 PIN 防止冒用、用完清理防止泄露。19 步环环相扣,缺一环都不行。
这就是国家标准的严谨之处。这套接口遵循 GM/T 0016-2023《智能密码钥匙密码应用接口规范》,是国内密码设备必须遵守的”通用语言”。无论你用飞天、卫士通还是龙脉的钥匙,代码逻辑都是一样的。
就像成功者说的:“优秀的公司赚取利润,伟大的公司赢得人心。”
而我们要做的,就是用这 19 步,赢得数字世界的信任。
我是利刃信安,网络安全和密码安全、数据安全领域的小白。
如果你觉得今天这篇有收获,欢迎点赞、在看、转发三连,我们下篇见
点赞
在看
转发
如有疑问,请联系:Mannix6
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:利刃信安 利刃信安 利刃信安《把智能密码钥匙的 19 步签名流程,讲给你听》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论