一、任务详情
密码引擎API的主要标准和规范包括:
- 1 微软的Crypto API
- 2 RAS公司的PKCS#11标准
- 3 中国商用密码标准:GMT 0016-2012 智能密码钥匙密码应用接口规范,GMT 0018-2012密码设备应用接口规范等
- 研究以上API接口,总结他们的异同,并以龙脉GM3000Key为例,写出调用不同接口的代码,提交博客链接和代码链接。
内容:
- 0 查找各种标准的原始文档,研究学习(至少包含Crypto API,PKCS#11,GMT 0016-2012,GMT 0018-2012)(5分)
- 1 总结这些API在编程中的使用方式(5分)
- 2 列出这些API包含的函数,进行分类,并总结它们的异同(10分)
- 3 以龙脉GM3000Key为例,写出调用不同接口的代码(Crypto API,PKCS#11,SKF接口),把运行截图加入博客,并提供代码链接(10分)
二、查找研究学习原始文档
三、总结这些API在编程中的使用方式
CryptoAPI
微软的CryptoAPI是PKI推荐使用的加密 API。其功能是为应用程序开发者提供在Win32环境下使用加密、验证等安全服务时的标准加密接口。CryptoAPI处于应用程序和CSP(cryptographic service provider)之间。
CryptoAPI的编程模型同Windows系统的图形设备接口 GDI比较类似,其中加密服务提供者CSP等同于图形设备驱动程序 ,加密硬件(可选)等同于图形硬件,其上层的应用程序也类似,都不需要同设备驱动程序和硬件直接打交道。
CryptoAPI共有五部分组成:简单消息函数(Simplified Message Functions)、低层消息函数(Low-level Message Functions)、基本加密函数(Base Cryptographic Functions)、证书编解码函数(Certificate Encode/Decode Functions)和证书库管理函数(Certificate Store Functions)。其中前三者可用于对敏感信息进行加密或签名处理,可保证网络传输信心的私有性;后两者通过对证书的使用,可保证网络信息交流中的认证性。
PKCS#11
我们把Cryptoki 确定为应用程序与各种各样的便携式密码设备(基于智能卡、PCMCIA卡以及智能软盘)间的一种接口。
Cryptoki 的主要目标是一个低级程序接口,该接口将设备的细节抽象化,并把密码设备的通用模型—密码令牌,或简称令牌—提供给应用程序。
Cryptoki的通用模型如下图所示。模型从一个或多个必须执行某些密码操作的应用程序开始,以一个或多个密码设备结束(在密码设备上执行某些或全部操作)。一个用户可涉及也可不涉及一个程序。
Cryptoki 为一个或多个密码设备提供一个接口,这些设备通过大量的槽在系统中运行。每个对应于一个物理阅读器或另一个设备接口的槽可包含一个令牌。当一台密码设备存在于阅读器中,一个令牌就存在于该槽中。当然,由于Cryptoki提供槽和令牌的逻辑视图,所以可能有其它的物理译码。多个槽可能共享一个阅读器。问题在于一个系统有相当多的槽,应用程序能连接到这些槽的其中任何一个或全部槽的令牌上。
Cryptoki的令牌逻辑视图是一个能存储对象和能执行密码函数的设备。Cryptoki定义如下三个对象:数据、证书和密钥。数据对象由应用程序定义。一个证书对象存储一个证书。一个密钥对象存储一个密码密钥。密钥可以是一个公共密钥、一个私钥或是一个保密密钥,每个种类的密钥在专用机制中使用其的辅助型。令牌的这种逻辑视图如下图所示:
SKF
针对支持国密算法USB KEY设备的应用,我国颁布一个行业标准《智能密码钥匙应用接口规范》(GM/T0016-2012),市面上销售的国密算法的USB KEY设备必须支持这个接口规范。因此,只要根据这个规范开发的应用程序,就可以兼容使用不同厂家及品牌的USB KEY产品。由于此规范中函数名称都以SKF开头,所以我们一般把按照此规范提供的设备开发接口库叫做SKF库或SKF接口。
智能密码钥匙密码应用接口位于智能密码钥匙应用程序与设备之间,如下图所示
一个设备中存在设备认证密钥和多个应用,应用之间相互独立。设备的逻辑结构如下图所示
应用由管理员PIN,用户PIN、文件和容器组成,可以存在多个文件和多个容器。每个应用维护各自的与管理员PIN和用户PIN相关的权限状态。
一个应用的逻辑结构如下图所示
容器中存放加密密钥对、签名密钥对和会话密钥。其中加密密钥对用于保护会话密钥,签名密钥对用于数字签名和验证,会话密钥用于数据加解密和MAC运算。容器中也可以存放与加密密钥对对应的加密数字证书和与签名密钥对对应的签名数字证书。其中,签名密钥对由内部产生,加密密钥对由外部产生并安全导入,会话密钥可由内部产生或者由外部产生并安全导入。
四、列出这些API包含的函数,进行分类,并总结它们的异同
CryptoAPI
密码服务提供者CSP函数
CryptoAPI的密码服务提供者函数主要包括6个函数。连接或断开CSP函数CryptAcquireContext、CryptReleaseContext,枚举CSP函数CryptEnumProviders,获得或设置默认CSP函数CryptGetDefaultProvider、CryptSetProvider,获取或设置CSP参数函数CryptGetProvParam、CryptSetProvParam。
-
连接CSP函数 CryptAcquireContext
函数功能:连接CSP,获得指定CSP的密钥容器的句柄。
-
枚举CSP函数 CryptEnumProviders
函数功能:枚举计算机上的所有CSP。此函数可以得到第一个或下一个可用的CSP。如果循环调用可以得到计算机上所有可用的CSP。 -
获得默认CSP函数 CryptGetDefaultProvider
函数功能:获得系统默认的CSP。
-
设置默认CSP函数 CryptSetProvider
函数功能:设置系统默认的CSP。
-
获得CSP参数属性函数 CryptGetProvParam
函数功能:获得CSP各种参数属性。
-
设置CSP参数函数 CryptSetProvParam
函数功能:设置CSP各种参数属性。
-
断开CSP函数 CryptReleaseContext
函数功能:断开CSP,释放CSP句柄,和 CryptAcquireContext相对应。
示例枚举CSP并获得默认CSP的参数处理过程如下图所示
密钥的产生与交换函数
CryptoAPI密钥产生和交换函数主要有生成密钥函数 CryptGenKey、派生密钥函数CryptDeriveKey、销毁密钥函数CryptDestoryKey、复制密钥函数CryptDuplicateKey、导出密钥函数CryptExportKey、导入密钥函数CryptImportKey、获得密钥参数函数CryptGetKeyParam、设置密钥参数函数CryptSetKeyParam、产生随机函数 CryptGenRandom。
-
生成函数 CryptGenKey
函数功能:产生一个随机的对称或非对称算法的密钥。 -
派生密钥函数 CryptDeriveKey
函数功能:根据基础数据派生一对称密钥(会话密钥)。 -
销毁密钥函数 CryptDestroyKey
函数功能:销毁密钥。
-
复制密钥函数 CryptDuplicateKey
函数功能:复制一个密钥。产生一个密钥的拷贝,包括其状态。 -
导出密钥函数 CryptExportKey
函数功能:从CSP导出密钥或密钥对。
-
导入密钥函数 CryptlmportKey
函数功能:把BLOB数据导入的CSP。该函数可以导入会话密钥、公钥、或者公/私钥对。 -
获得密钥参数函数 CryptGetKeyParam
函数功能:获得key句柄的各项参数。
-
获得密钥参数函数 CryptSetKeyParam
函数功能:设置key句柄的各项参数。
-
获得密钥参数函数 CryptGenRandom
函数功能:生成随机数。
示例密钥产生和交换实例处理过程如下图所示
数据的加密与解密函数
CryptoAPI利用CryptEncrypt函数实现数据加密,利用CryptDecrypt实现数据解密。调用这2个函数前必须指定一个密钥,这个密钥可以由CryptGenKey、CryptDeriveKey或CryptImportKey产生。也可用CryptSetKeyParam函数指定额外的加密参数。
- 数据加密函数 CryptEncrypt
函数功能:使用hKey指定的密钥和算法加密数据。 - 数据解密函数 CryptDecrypt
函数功能:使用hKey指定的密钥和算法对加密数据解密。
示例数据加密处理过程如下图所示
示例数据解密处理过程如下图所示
哈希和数字签名函数
CryptoAPI提供的哈希和数字签名函数包括创建哈希函数CryptCreateHash、销毁哈希CryptDestroyHash、复制哈希函数CryptDuplicateHash、获得哈希参数函数CryptGetHashParam,设置哈希参数函数CryptSetHashParam、哈希会话密钥函数 CryptHashSessionKey、哈希数据函数CryptHashData、对哈希签名函数CryptSignHash和对哈希验证签名函数CryptVerifySignature。
-
创建哈希函数 CryptCreateHash
函数功能:创建哈希。
-
销毁哈希 CryptDestroyHash
函数功能:销毁哈希对象。
-
复制哈希函数 CryptDuplicateHash
函数功能:复制一个哈希对象。
-
获得哈希参数函数 CryptGetHashParam
函数功能:获得哈希对象的参数。
-
设置哈希参数函数 CryptSetHashParam
函数功能:设置哈希对象的参数。
-
哈希会话密钥函数 CryptHashSessionKey
函数功能:对一个会话密钥进行哈希,把它加到指定的哈希对象中。 -
哈希数据函数 CryptHashData
函数功能:对数据进行哈希操作,此函数可以反复调用。 -
对哈希签名函数 CryptSignHash
函数功能:对哈希对象进行签名。
-
对哈希验证签名函数 CryptVerifySignature
函数功能:验证哈希签名。
示例对数据签名和验证的流程如下图所示
证书和证书库函数
CryptoAPI证书和证书库函数主要包括打开证书库函数CertOpenStore、关闭证书库函数CertCloseStore、从证书库枚举证书函数CertEnumCertificatesInStore、从证书库查找证书函数CertFindCertificateInStore、创建证书句柄函数 CertCreateCertificateContext、释放证书句柄函数CertFreeCertificateContext、获得证书句柄属性函数CertGetCertificateContextProperty、设置证书句柄属性函数CertSetCertificateContextProperty和获得证书主题名称函数CertGetNameString。
-
打开证书库函数 CertOpenStore
函数功能:根据证书库类型,打开证书库。
-
关闭证书库函数 CertCloseStore
函数功能:关闭证书库。
-
从证书库枚举证书函数 CertEnumCertificateslnStore
函数功能:枚举证书库中的证书。该函数通过循环调用,可以枚举证书库内的全部证书,上一次的返回,是下一次的pPrevCertContext,直到返回值为NULL。 -
从证书库查找证书函数 CertFindCertificatelnStore
函数功能:从证书库中查找指定的证书。
-
创建证书句柄函数 CertCreateCertificateContext
函数功能:由证书数据创建证书句柄。
-
释放证书句柄函数 CertFreeCertificateContext
函数功能:释放证书句柄。
-
获得证书句柄属性函数 CertGetCertificateContextProperty
函数功能:获得证书句柄属性。
-
设置证书句柄属性函数 CertSetCertificateContextProperty
函数功能:设置证书句柄属性。
-
获得证书主题名称函数 CertGetNameString
函数功能:从证书中获得主题或颁发者的名称。
示例枚举证书库并输出其属性的处理流程如下图所示
PKCS#11
种类 | 函数 | 描述 |
---|---|---|
通用目的函数 | C_Initialize | 初始化 Cryptoki |
C_Finalize | 整理各种适合 Cryptoki的资源 | |
C_GetInfo | 获得关于Cryptoki的通用信息 | |
C_GetFunctionList | 获得Cryptoki 库函数的进入点 | |
槽和令牌管理函数 | C_GetSlotList | 获得系统中槽的名单 |
C_GetSlotInfo | 获得关于特殊槽的信息 | |
C_GetTokenInfo | 获得关于特殊令牌的信息 | |
C_WaitForSlotEvent | 等待槽事件(令牌插入,转移等) 的发生 | |
C_GetMechanismList | 获得由令牌支持的机制的名单 | |
C_GetMechanismInfo | 获得关于特殊机制的信息 | |
C_InitToken | 初始化一个令牌 | |
C_InitPIN | 初始化普通用户的 PIN | |
C_SetPIN | 改变现在用户的PIN | |
会话管理函数 | C_OpenSession | 打开一个应用程序和特殊令牌之间的连接或安装一个应用程序呼叫返回令牌插入 |
C_CloseSession | 关闭一个会话 | |
C_CloseAllSessions | 用令牌关闭所有的会话 | |
C_GetSessionInfo | 获得关于会话的信息 | |
C_GetOperationState | 获得会话的加密操作状态 | |
C_SetOperationState | 设置会话的加密操作状态 | |
C_Login | 注册一个令牌 | |
C_Logout | 从一个令牌注销 | |
对象管理函数 | C_CreateObject | 建立一个对象 |
C_CopyObject | 建立一个对象的拷贝 | |
C_DestroyObject | 销毁一个对象 | |
C_GetObjectSize | 获取字节中一个对象的大小 | |
C_GetAttributeValue | 获取一个对象的属性值 | |
C_SetAttributeValue | 改变一个对象的属性值 | |
C_FindObjectsInit | 初始化一个对象的搜索操作 | |
C_FindObjects | 继续一个对象搜索操作 | |
C_FindObjectsFinal | 完成一个对象搜索操作 | |
加密函数 | C_EncryptInit | 初始化一个加密操作 |
C_Encrypt | 加密单部分数据 | |
C_EncryptUpdate | 继续一个多部分加密操作 | |
C_EncryptFinal | 完成一个多部分加密操作 | |
解密函数 | C_DecryptInit | 初始化一个解密操作 |
C_Decrypt | 解密单部分加密数据 | |
C_DecryptUpdate | 继续一个多部分解密操作 | |
C_DecryptFinal | 完成一个多部分解密操作 | |
消息摘要函数 | C_DigestInit | 初始化一个消息摘要操作 |
C_Digest | 摘要单部分数据 | |
C_DigestUpdate | 继续一个多部分摘要操作 | |
C_DigestKey | 摘要一个密钥 | |
C_DigestFinal | 完成一个多部分摘要操作 | |
签名和MACing | C_SignInit | 初始化一个签名操作 |
C_Sign | 签名单部分数据 | |
C_SignUpdate | 继续一个多部分签名操作 | |
C_SignFinal | 完成一个多部分签名操作 | |
C_SignRecoverInit | 初始化一个签名操作,在操作中数据能从签名中恢复 | |
C_SignRecover | 签名单部分数据,在操作中数据能从签名中恢复 | |
验签和MACs | C_VerifyInit | 初始化一个鉴定操作 |
C_Verify | 在单部分数据上鉴定一个签名 | |
C_VerifyUpdate | 继续一个多部分鉴定操作 | |
C_VerifyFinal | 完成一个多部分鉴定操作 | |
C_VerifyRecoverInit | 初始化一个鉴定操作,在操作中数据能从签名中恢复 | |
C_VerifyRecover | 在单部分数据上鉴定一个签名,在操作中数据能从签名中恢复 | |
双重目的的加密 | C_DigestEncryptUpdate | 继续类似的多部分摘要和加密操作 |
C_DecryptDigestUpdate | 继续类似的多部分解密和摘要操作 | |
C_SignEncryptUpdate | 继续类似的多部分签名和加密操作 | |
C_DecryptVerifyUpdate | 继续类似的多部分解密和鉴定操作 | |
密钥管理函数 | C_GenerateKey | 产生一个保密密钥 |
C_GenerateKeyPair | 产生一个公共/私钥对 | |
C_WrapKey | 加密一个密钥 | |
C_UnwrapKey | 解密一个密钥 | |
C_DeriveKey | 从基础密钥派生一个密钥 | |
随机数生成函数 | C_SeedRandom | 把一个附加种子材料加入随机数字生成器 |
C_GenerateRandom | 生成随机数据 | |
并行功能管理函数 | C_GetFunctionStatus | 经常返回 CKR_FUNCTION_NOT_PARALLEL的遗产函数 |
C_CancelFunction | 经常返回 CKR_FUNCTION_NOT_PARALLEL的遗产函数 | |
呼叫返回函数 | Cryptoki中应用程序提供的处理通知的函数 |
SKF
设备管理函数
访问控制函数
应用管理函数
文件管理函数
容器管理函数
密码服务函数
得到国密证书
根据国密标准,一种设备类型可以有多个设备(Device),每一个设备内可以有多个应用(Application),每一个应用里可以有多个容器(Container),每个容器里可以有一对证书(Certificate):签名证书和加密证书。
因此,应按照下列顺序依次调用相关接口:
1.SKF_EnumDev(BOOL bPresent, LPSTR szNameList, ULONG *pulSize);
调用这个方法用来遍历当前电脑上的设备,这个方法的第一个参数一般传TRUE,表示遍历的是插上的设备;第二个参数就是返回的设备名称列表;第三个参数是设备名称列表缓冲区长度。按照惯例,这个方法应该被调用两次:第一次szNameList传NULL,pulSize返回长度;第二次给szNameList分配pulSize长度,返回设备列表,每个设备的名称以单个’\0’结束,以双’\0’表示列表的结束。
2.SKF_ConnectDev(LPSTR szName, DEVHANDLE *phDev);
通过循环调用这个方法用来连接每一个具体的设备,szName为设备名,即上面方法得到的列表中的设备名;返回phDev为设备句柄。
3.SKF_EnumApplication(DEVHANDLE hDev, LPSTR szAppNameList,ULONG *pulSize);
得到设备句柄后,再通过此方法枚举得到设备里的应用列表。hDev为连接设备时返回的设备句柄;szAppNameList返回应用名称列表;pulSize是列表缓冲区长度。这个方法也是照例要调用两次,不再赘述。同样,每个应用的名称以单个’\0’结束,以双’\0’表示列表的结束。
4.SKF_OpenApplication(DEVHANDLE hDev, LPSTR szAppName, HAPPLICATION *phApplication);
通过循环调用此方法打开应用列表里的每一个应用,hDev为连接设备时返回的设备句柄;szAppName是要打开的应用名称;phApplication为返回的应用句柄。
5.SKF_EnumContainer(IN HAPPLICATIONhApplication, OUT LPSTRszContainerNameList, OUT ULONG*pulSize)
拿到应用句柄后,我们就可以用此方法遍历应用中的所有容器了。hApplication是应用句柄;szContainerNameList是返回的容器名称列表;pulSize是列表长度;后面的就不用了多说了。
6.SKF_OpenContainer(HAPPLICATION hApplication,LPSTR szContainerName,HCONTAINER *phContainer);
循环调用此方法打开每一个容器。hApplication是应用句柄;szContainerName是要打开的容器名称;phContainer是返回的容器句柄。
7.SKF_ExportCertificate(HCONTAINER hContainer, BOOL bSignFlag, BYTE* pbCert, ULONG *pulCertLen);
最后就可以通过这个方法取得每个容器里的证书。hContainer是容器句柄;bSignFlag为导出的证书类型; TRUE表示导出的是签名证书;FALSE表示导出加密证书。pbCert为返回的证书数据,pulCertLen是证书数据的长度。同样需两次调用。
数字签名
在数字签名时,要指定签名所使用的证书。通过遍历本机上的证书,与签名用的证书进行对比,定位到签名证书在USBKEY中的位置,得到设备、应用和容器的句柄,然后使用证书的私钥进行签名。遍历对比的过程可参见上一节的内容。另外,由于数字签名会用到私钥,因此这里需要验证口令。
1.SKF_VerifyPIN(HAPPLICATION hApplication, ULONG ulPINType, LPSTR szPIN, ULONG *pulRetryCount);
此方法用来验证证书所在应用的PIN码,及上面说的口令,为后面的签名取得权限。hApplication是应用句柄;ulPINType是PIN类型,可以为0是管理员账户,1为普通用户,这个参数一般选择1。szPIN值是PIN码,pulRetryCount为出错后返回的重试次数。
2.SKF_ExportPublicKey(HCONTAINER hContainer, BOOL bSignFlag, BYTE* pbBlob, ULONG* pulBlobLen);
这个方法用来导出容器中的签名公钥,hContainer为证书所在容器句柄;bSignFlag 为导出密钥类型,TRUE表示导出签名公钥,FALSE表示导出加密公钥,这里选择TRUE;pbBlob为返回公钥的数据;pulBlobLen为数据的长度。这里这个方法可以不用调用两次,因为公钥结构是已知的,其长度也是固定的,因此可以直接为pbBlob分配固定长度的数据,以返回公钥。
3.SKF_DigestInit(DEVHANDLE hDev, ULONG ulAlgID, ECCPUBLICKEYBLOB *pPubKey, unsigned char *pucID, ULONG ulIDLen, HANDLE *phHash);
此方法进行杂凑(国密标准里把摘要称之为杂凑)运算初始化,并指定计算消息杂凑的算法。hDev为设备句柄;ulAlgID是杂凑算法标识,这里选择SGD_SM3(0x00000001),表明使用SM3算法;pPubKey为签名用证书公钥数据;pucID为签名者的ID值;ulIDLen是签名者的ID值的长度;phHash为返回的杂凑对象句柄。加入签名者ID值是SM2数字签名的一个重要特征,默认使用"1234567812345678"这个字符串值。
4.SKF_Digest(HANDLE hHash, BYTE *pbData, ULONG ulDataLen, BYTE *pbHashData, ULONG *pulHashLen);
初始化后,调用此方法进行数据杂凑运算。hHash是SKF_DigestInit方法返回的杂凑对象句柄; pbData为产生签名的原文,ulDataLen是原文数据的长度,pbHashData返回杂凑数据; pulHashLen返回杂凑结果的长度。同样,因为杂凑数据的长度都是固定的,这里同样可以为pbHashData事先分配固定长度,而不用再调用两遍。
注意,如果进行杂凑的数据是分组的,那就得使用下面两个方法:
SKF_DigestUpdate(HANDLE hHash, BYTE *pbData, ULONG ulDataLen);
SKF_DigestFinal(HANDLE hHash, BYTE *pHashData, ULONG *pulHashLen);
对每一组数据都使用SKF_DigestUpdate,最后调用SKF_DigestFinal返回杂凑值。当然,在数字签名运算中不存在分块计算签名的情况,所以这里也不会把数据分块杂凑。
5.SKF_ECCSignData(HCONTAINER hContainer, BYTE *pbData, ULONG ulDataLen, PECCSIGNATUREBLOB pSignature);
最后调用此方法进行数字签名。hContainer用来签名的私钥所在容器句柄,也就是遍历对比证书得到的容器句柄;pbData是被签名的数据;ulDataLen是被签名数据长度,必须小于密钥模长; pbSignature为返回的签名值。
验证签名
1.SKF_CreateContainer(HAPPLICATION hApplication, LPSTR szContainerName, HCONTAINER *phContainer)
调用此方法创建一个临时容器。hApplication为容器所在的应用句柄;szContainerName是ASCII字符串,表示所建立容器的名称,最大长度不能超过64字节;phContainer是返回所建立容器的容器句柄。
2.SKF_ImportCertificate(HCONTAINER hContainer, BOOL bSignFlag, BYTE* pbCert, ULONG ulCertLen);
将签名用的证书导入到容器中。hContainer为容器句柄,即用上一方法创建的临时容器;bSignFlag为证书类型,TRUE表示签名证书,FALSE表示加密证书,这里选TRUE;pbCert,是证书数据;ulCertLen为证书数据长度;
3.SKF_ExportPublicKey(HCONTAINER hContainer, BOOL bSignFlag, BYTE* pbBlob, ULONG* pulBlobLen);
导出公钥。
4.SKF_DigestInit(DEVHANDLE hDev, ULONG ulAlgID, ECCPUBLICKEYBLOB *pPubKey, unsigned char *pucID, ULONG ulIDLen, HANDLE *phHash);
杂凑初始化。
5.SKF_Digest(HANDLE hHash, BYTE *pbData, ULONG ulDataLen, BYTE *pbHashData, ULONG *pulHashLen);
杂凑运算。
6.SKF_ECCVerify(DEVHANDLE hDev , ECCPUBLICKEYBLOB* pECCPubKeyBlob, BYTE *pbData, ULONG ulDataLen, PECCSIGNATUREBLOB pSignature);
进行签名验证。hDev是设备句柄;pECCPubKeyBlob是公钥数据结构,即第3步得到的公钥; pbData为待验证签名的数据;ulDataLen是待验证签名数据长度;pbSignature待验证的签名值。
7.SKF_DeleteContainer(HAPPLICATION hApplication, LPSTR szContainerName);
删除临时容器。hApplication为容器所在的应用句柄;szContainerName为容器名称。
五、以龙脉GM3000Key为例,写出调用不同接口的代码(Crypto API,PKCS#11,SKF接口),把运行截图加入博客,并提供代码链接
CryptoAPI
需要使用x64编译
EncryptDecryptFile
EncrypteFile
PKCS#11
需要使用win32编译