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

admin 2026-10-18 03:19:41 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

把智能密码钥匙的 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步签名流程,讲给你听" 网络安全文章

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

本文详细拆解了智能密码钥匙进行ECC数字签名的19步标准流程,涵盖设备枚举连接锁定、应用容器操作、SM2预处理三步曲、PIN校验及签名执行等关键环节。每步均引用SKF标准函数原型与技术要点,强调严格按序调用与资源清理。文章为开发者提供了可直接参考的API调用指南,具有强实操性。
云原生未授权系列之kubelet未授权:匿名执行节点命令" 网络安全文章

云原生未授权系列之kubelet未授权:匿名执行节点命令"

本文详细分析了Kuberneteskubelet10250端口因开启匿名访问且授权模式设为AlwaysAllow导致的未授权命令执行漏洞。攻击者可通过/pods端点获取节点所有Pod信息,再利用/run端点在容器内匿名执行任意命令,进而窃取ServiceAccountToken横向移动至kube-apiserver,最终通过创建特权Pod逃逸宿主机实现集群接管。修复建议包括关闭匿名访问、启用Webhook授权、网络隔离10250端口、启用NodeRestriction准入控制及定期使用kube-bench
免杀对抗之睡眠混淆技术深度解析" 网络安全文章

免杀对抗之睡眠混淆技术深度解析"

本文深度解析了免杀对抗中的睡眠混淆技术,重点介绍了Ekko技术通过Windows定时器队列实现内存加密解密,避免了RX内存特征。文章从技术细节到代码实现,详细阐述了内存对抗的演进路径,为红队武器研发提供了可操作建议。
评论:0   参与:  0