把智能密码钥匙的19步签名流程,讲给你听

admin 2026-08-22 04:45:54 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细拆解了智能密码钥匙进行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 步签名流程,讲给你听》

有偿征集大数据应用思路 网络安全文章

有偿征集大数据应用思路

文章总结: 该文档为一篇公众号文章,作者声称捡到含100万条LBS数据的U盘,有偿征集大数据应用思路。鼓励用于城市治理、应急管理等社会价值高的领域,严禁侵犯个体
评论:0   参与:  0