实验一-密码引擎-加密API研究

一、Crypto API

         我研究学习的文档是娄老师在微信群和云班课资源区发布的文件PKCS#11、MT 0016-2012和GMT 0018-2012,以及自己在网络搜索查阅到的Crypto API相关知识博客。

(一)学习Crypto API

相关资料:Microsoft官网,Crypto API

        Windows Crypto API是Microsoft 公司提出的安全加密应用服务框架,也是PKI推荐使用的加密 API。它提供了在Win32 环境下使用认证、编码、加密和签名等安全服务时的标准加密接口,用于增强应用程序的安全性与可控性。应用开发者可以在不了解复杂的加密机制和加密算法的情况下,简便、快速地开发出标准、通用和易于扩展的安全加密应用程序。Crypto API 提供的功能主要有:密钥管理、数据加密和解密、数字签名和验证、证书管理、可信根证书管理、数据编码和解码、数字证书编码和解码、PKCS#7标准格式编码和解码等。

        微软加密服务体系包含3层结构和两个接口,分别为应用程序层、操作系统层(OS)、加密服务提供者层(Cryptographic Service Provider,CSP)、CryptoAPI接口和加密服务提供者接口(CSPI)

        应用程序层通过CryptoAPI接口与操作系统通信,操作系统通过加密服务提供者接口(CSPI)与CSP通信。其编程模型同Windows系统的图形设备接口GDI比较类似,其中加密服务提供者CSP等同于图形设备驱动程序 ,加密硬件(可选)等同于图形硬件,其上层的应用程序也类似,都不需要同设备驱动程序和硬件直接打交道。CryptoAPI接口面向应用系统,而CryptoSPI 面向加密模块开发商,CryptoAPI 统一由Windows 提供,而底层的CSP由各开发商提供。这样的分层体系结构,使应用系统不必关心底层的加密实现细节和实现方式(软件或硬件),降低了集成难度,并且在一个系统中可以同时加载多个CSP。

        CryptoAPI函数使用加密服务提供程序(CSP)进行加密和解密,并提供密钥存储和安全性。它是真正实行加密相关服务的独立模块,既可以由软件实现也可以由硬件实现,但是必须符合CryptoAPI接口的规范。

       CSP至少由一个动态链接库(DLL)和一个签名文件组成:签名文件是确保CryptoAPI识别CSP所必需的;CryptoAPI会定期验证此签名,以确保检测到对CSP的任何篡改。

       同时,每个CSP都有一个名字和一个类型,名字必须唯一的,这样便于CryptoAPI找到对应的CSP。加密服务标准被分为不同的家族,每种家族包含自己的一系列数据格式和协议,不同的家族有不同的数据格式或协议等。在CryptoAPI中,每种CSP类型代表不同的家族。目前已经有9种CSP类型,并且还在增长,不同类型支持的密钥交换算法、签名算法、对称加密算法和Hash算法等如下表所示。

CSP类型交换算法签名算法对称加密算法Hash算法
PROV_RSA_FULL RSA RSA RC2 RC4 MD5 SHA
PROV_RSA_SIG none RSA none MD5 SHA
PROV_RSA_SCHANNEL RSA RSA RC4 DES Triple DES MD5 SHA
PROV_DSS DSS none DSS MD5 SHA
PROV_DSS_DH DH DSS CYLINK_MEK MD5 SHA
PROV_DH_SCHANNEL DH DSS DES Triple DES MD5 SHA
PROV_FORTEZZA KEA DSS Skipjack SHA
PROV_MS_EXCHANGE RSA RSA CAST MD5
PROV_SSL RSA RSA Varies Varies

         CryptoAPI体系架构共由五大主要部分组成:基本CSP函数、证书编解码函数、证书库管理函数、简单消息函数、底层消息函数。其结构图如下图所示。

         CryptoAPI基本功能主要有:加密密钥、数据编码/解码、数据加/解密、哈希与数字签名、数字证书、数字消息

(二)使用方式

        实现用crypto API加密解密可以用下列流程图进行大概的描述:

1. 创建密钥容器,得到CSP句柄

         每一个CSP都有一个名字和一个类型,并且名字保证唯一,所以可以通过名字和类型得到一个CSP。而密钥就放在密钥容器,但是密钥容器并不是一开始就存在的,需要用户去创建。下面的代码实现以上功能(得到CSP即密码容器)。

if(CryptAcquireContext(
&hCryptProv,               // 返回CSP句柄
UserName,                  // 密码容器名
NULL,                      // NULL时使用默认CSP名(微软RSA Base Provider)
PROV_RSA_FULL,             // CSP类型
0))                        // Flag values
{
//以UserName为名的密钥容器存在,那么我们已经得到了CSP的句柄
    printf("A crypto context with the %s key container /n", UserName);
    printf("has been acquired./n/n");
}
else //如果密钥容器不存在,我们需要创建这个密钥容器
{
   if(CryptAcquireContext(
      &hCryptProv,
      UserName,
      NULL,
      PROV_RSA_FULL,
      CRYPT_NEWKEYSET)) //创建以UserName为名的密钥容器
   {
       //创建密钥容器成功,并得到CSP句柄
      printf("A new key container has been created./n");
   }
   else
   {
      HandleError("Could not create a new key container./n");
    }
} // End of else

 

        以后的加解密等操作,都将在这个CSP上进行。可以使用下列代码进行删除密钥容器的操作。

CryptAcquireContext(&hCryptProv, 
                    userName, 
                    NULL,
                    PROV_RSA_FULL,
                    CRYPT_DELETEKEYSET);

2. 文件加解密和签名验签

主要函数:(1)主函数:void main(void);(2) 加密文件:BOOL EncryptFile(PCHAR szSource, PCHAR szDestination, PCHAR szPassword);(3)解密文件:BOOL DecryptFile(PCHAR szSource, PCHAR szDestination, PCHAR szPassword);(4)签名文件:BOOL SignFile (PCHAR szSource, PCHAR szDestination);(5)验证签名:BOOL VerifyFile (PCHAR szSource, PCHAR szDestination);(6)错误处理:void HandleError(char *s);

加密文件
1)打开源文件
hSource = fopen(szSource,"rb")
(2)取得密钥容器(CSP)句柄
CryptAcquireContext(&hCryptProv,NULL,NULL,PROV_RSA_FULL,0)
(3)根据用户输入的密码创建一个会话密钥(即对称密钥,用于对原文件加密)
CryptCreateHash(hCryptProv,CALG_MD5, 0, 0, &hHash)//创建一个Hash对象
CryptHashData(hHash, (BYTE *)szPassword, strlen(szPassword), 0)//用用户输入的密码产生一个散列
CryptDeriveKey(hCryptProv, ENCRYPT_ALGORITHM,hHash, KEYLENGTH, &hKey))//通过散列生成一个会话密钥
CryptDestroyHash(hHash);//销毁Hash对象4)加密数据文件
CryptEncrypt(
        hKey,          // 密钥
        0,             // 如果数据同时进行散列和加密,这里传入一个散列对象
        feof(hSource), // 如果是最后一个被加密的块,输入 TRUE. 如果不是输
                       // 入 FALSE 这里通过判断是否到文件尾来决定是否为最后一块
        0,             // 保留
        pbBuffer,      // 输入被加密数据,输出加密后的数据
        &dwCount,      // 输入被加密数据实际长度,输出加密后数据长度
        dwBufferLen)   //pbBuffer 的大小5)清理工作,如释放Buffer空间、密钥句柄、CSP句柄等。
if(pbBuffer)
    free(pbBuffer);
if(hKey)
    CryptDestroyKey(hKey);
if(hHash)
    CryptDestroyHash(hHash);
if(hCryptProv)
    CryptReleaseContext(hCryptProv, 0);
.....
解密文件
1)打开加密文件(同上)
(2)取得密钥容器(CSP)句柄(同上)
(3)根据用户输入的密码创建一个会话密钥(即对称密钥,用于对原文件解密)(同上)
(4)解密数据文件
CryptDecrypt(
    hKey,               // 密钥
    0,                    // 如果数据同时进行散列和加密,这里传入一个散列对象
    feof(hSource),   // 如果是最后一个被加密的块,输入 TRUE. 如果不是输 .
                // 入 FALSE 这里通过判断是否到文件尾来决定是否为最后一块。
    0,                    // 保留
    pbBuffer,          // 输入被加密数据,输出加密后的数据
    &dwCount))           // 输入被加密数据实际长度,输出加密后数据长度5)清理工作,如释放Buffer空间、密钥句柄、CSP句柄等。(同上)
签名文件
1)打开源文件(同上)
(2)取得密钥容器(CSP)句柄(同上)
(3)取得签名用的密钥句柄(非对称RSA密钥)
CryptGetUserKey(
    hCryptProv,               //  我们已经得到的 CSP 句柄
    AT_SIGNATURE,            //  这里想得到 signature key pair
    &hKey))                       //  返回密钥句柄4)导出签名用密钥对的公钥,保存在pbKeyBlob中
CryptExportKey(hKey, NULL,PUBLICKEYBLOB, 0, pbKeyBlob,&dwBlobLen)
(5)计算数据文件的Hash值,保存在Hash对象hHash中
CryptCreateHash(hCryptProv,CALG_MD5,0,0,&hHash)//生成一个空的Hash对象
CryptHashData(hHash,pbBuffer,dwCount,0)//计算数据文件的Hash值,保存在Hash对象中6)对数据文件的Hash值进行签名,数字签名保存在pbSignature中
CryptSignHash(hHash, AT_SIGNATURE, NULL, 0, pbSignature, &dwSigLen)
(7)清理工作,如释放Buffer空间、密钥句柄、CSP句柄等。(同上)
验证签名
1)打开文件(同上)
(2)取得密钥容器(CSP)句柄(同上)
(3)导入 pbKeyBlob 公钥
CryptImportKey(hCryptProv, pbKeyBlob, dwBlobLen, 0, 0, &hPubKey)
(4)计算数据文件的Hash值,保存在Hash对象hHash中。(同上)
(5)验证数字签名
CryptVerifySignature(hHash, pbSignature, dwSigLen,hPubKey,NULL, 0)
(6)清理工作,如释放Buffer空间、密钥句柄、CSP句柄等。(同上)
举例加密文件:
#include
#include
#include
#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)
#define KEYLENGTH  0x00800000
void HandleError(char *s);
//-------------------------------------------------------------------//  These additional #define statements are required.
#define ENCRYPT_ALGORITHM CALG_RC4
#define ENCRYPT_BLOCK_SIZE 8
//   Declare the function EncryptFile. The function definition
//   follows main.
BOOL EncryptFile(
        PCHAR szSource,
       PCHAR szDestination,
       PCHAR szPassword);
//-------------------------------------------------------------------//   Begin main.
void main(void) {    
    CHAR szSource[100];    
    CHAR szDestination[100];   
    CHAR szPassword[100];      
    printf("Encrypt a file. \n\n");   
    printf("Enter the name of the file to be encrypted: ");
    scanf("%s",szSource); 
    printf("Enter the name of the output file: ");
    scanf("%s",szDestination);  
    printf("Enter the password:");    
    scanf("%s",szPassword);       
// Call EncryptFile to do the actual encryption.            
if( EncryptFile(szSource, szDestination, szPassword) ){                 
    printf("Encryption of the file %s was a success. \n", szSource);
    printf("The encrypted data is in file %s.\n",szDestination);
}          
Else{                
    HandleError("Error encrypting file!");     
}
} // End of main
//-------------------------------------------------------------------//   Code for the function EncryptFile called by main.
static BOOL EncryptFile(                                            
    PCHAR szSource,
    PCHAR szDestination,
    PCHAR szPassword
)
//-------------------------------------------------------------------      
//   Parameters passed are:                                 
//   szSource,      the name of the input, a plaintext file.
//   szDestination, the name of the output, an encrypted file to be
//                   created.                               
//   szPassword, the password.
{
//-------------------------------------------------------------------      
//   Declare and initialize local variables.                
    FILE *hSource;   
    FILE *hDestination;             
    HCRYPTPROV hCryptProv;   
    HCRYPTKEY hKey;  
    HCRYPTHASH hHash;                     
    PBYTE pbBuffer;  
    DWORD dwBlockLen;        
    DWORD dwBufferLen;       
    DWORD dwCount;           
//-------------------------------------------------------------------      
// Open source file.  
if( hSource = fopen(szSource,"rb") ){                  
    printf("The source plaintext file, %s, is open. \n", szSource);
}
else{                  
    HandleError("Error opening source plaintext file!");  }      
//-------------------------------------------------------------------      
// Open destination file.     
if( hDestination = fopen(szDestination,"wb") ){                  
    printf("Destination file %s is open. \n", szDestination);    
}
else{                 
    HandleError("Error opening destination ciphertext file!");  
}         
//以下获得一个CSP句柄
if( CryptAcquireContext(        
    &hCryptProv,
    NULL,      
    NULL,           
    PROV_RSA_FULL,         
    0)
    ) {                        
    printf("A cryptographic provider has been acquired. \n");
    }                
else{                        
    if( CryptAcquireContext(               
    &hCryptProv,                   
    NULL,                    
    NULL,                    
    PROV_RSA_FULL,           
    CRYPT_NEWKEYSET))       
{                           
printf("A new key container has been created.\n");
}
Else
{
HandleError("Could not create a new key container.\n");
}      
//-------------------------------------------------------------------      
// Create a hash object.      
if(
CryptCreateHash( hCryptProv, CALG_MD5,0,0,&hHash))   
{       
printf("A hash object has been created. \n");   
}   
else   
{
HandleError("Error during CryptCreateHash!\n");   
}   
if(
CryptHashData(                
hHash,             
(BYTE *)szPassword,             
strlen(szPassword),             
0)
)
{
printf("The password has been added to the hash. \n");    
}           
else        
{                       
HandleError("Error during CryptHashData. \n");    
}  
if( CryptDeriveKey(
   hCryptProv,             
ENCRYPT_ALGORITHM,            
hHash,                  
KEYLENGTH,
&hKey)
)  
{                        
printf("An encryption key is derived from the password hash. \n");  
}     
else  
{                       
HandleError("Error during CryptDeriveKey!\n");    
}        
CryptDestroyHash(hHash);      
hHash = NULL;                  
dwBlockLen = 1000 - 1000 % ENCRYPT_BLOCK_SIZE;          
    if( ENCRYPT_BLOCK_SIZE > 1)
dwBufferLen = dwBlockLen + ENCRYPT_BLOCK_SIZE;     
else                     
dwBufferLen = dwBlockLen;           
if( pbBuffer = (BYTE *)malloc(dwBufferLen))
{                              
printf("Memory has been allocated for the buffer. \n");  
}                      
else                   
{                      
HandleError("Out of memory. \n");     
}
do {  dwCount = fread(pbBuffer, 1, dwBlockLen, hSource);
          if(ferror(hSource))
{
HandleError("Error reading plaintext!\n");    
}                                                
if(
!CryptEncrypt(hKey,0,feof(hSource), 0, pbBuffer,&dwCount, dwBufferLen)
)       
{                               
HandleError("Error during CryptEncrypt. \n");         
}                              
                          fwrite(pbBuffer, 1, dwCount, hDestination);                     if(ferror(hDestination))        
{                  
HandleError("Error writing ciphertext.");              
}                 
} while(!feof(hSource));
                        
if(hSource)                           
fclose(hSource);     
if(hDestination)
fclose(hDestination);                                  
if(pbBuffer)                          
free(pbBuffer);                
if(hKey)
  CryptDestroyKey(hKey);                                    
if(hHash) CryptDestroyHash(hHash);                  
if(hCryptProv)                             
CryptReleaseContext(hCryptProv, 0);    
return(TRUE);
} // End of Encryptfile

//-------------------------------------------------------------------
//  This example uses the function HandleError, a simple error
//  handling function, to print an error message to the standard error
//  (stderr) file and exit the program.
//  For most applications, replace this function with one
//  that does more extensive error reporting.

void HandleError ( char *s )
{   
fprintf(stderr,"An error occurred in running the program. \n");  
fprintf(stderr,"%s\n",s);   
fprintf(stderr, "Error number %x.\n", GetLastError());  
fprintf(stderr, "Program terminating. \n");   
exit(1);
} // End of HandleError

(三)API函数

服务提供者函数

应用程序使用服务提供者函数来连接和断开一个CSP。

CryptAcquireContext获得指定CSP的密钥容器的句柄
CryptContextAddRef 对HCRYPTPROV句柄增加一个应用计数
CryptEnumProviders 枚举当前计算机中的CSP
CryptEnumProviderTypes 枚举CSP的类型
CryptGetDefaultProvider 对于指定CSP类型的却省CSP
CryptGetProvParam 得到一个CSP的属性
CryptInstallDefaultContext 安装先前得到的HCRYPTPROV上下文作为当前却省的上下文
CryptReleaseContext 释放由CryptAcquireContext得到的句柄
CryptSetProvider和CryptSetProviderEx 为指定CSP类型指定一个却省的CSP
CryptSetProvParam 指定一个CSP的属性
CryptUninstallDefaultContext 删除先前由CryptInstallDefaultContext安装的却省上下文
密钥的产生和交换函数

密钥产生函数创建、配置和销毁加密密钥。他们也用于和其他用户进行交换密钥。

CryptAcquireCertificatePrivateKey对于指定证书上下文得到一个HCRYPTPROV句柄和dwKeySpec
CryptDeriveKey 从一个密码中派生一个密钥
CryptDestoryKey 销毁密钥
CryptDuplicateKey 制作一个密钥和密钥状态的精确复制
CryptExportKey 把CSP的密钥做成BLOB 传送到应用程序的内存空间中
CryptGenKey 创建一个随机密钥
CryptGenRandom 产生一个随机数
CryptGetKeyParam 得到密钥的参数
CryptGetUserKey 得到一个密钥交换或签名密钥的句柄
CryptImportKey 把一个密钥BLOB传送到CSP 中
CryptSetKeyParam 指定一个密钥的参数
编码/解码函数

有一些编码/解码函数,他们可以用来对证书、证书撤销列表、证书请求和证书扩展进行编码和解码。

CryptDecodeObject对lpszStructType结构进行解码
CryptDecodeObjectEx 对lpszStructType结构进行解码,此函数支持内存分配选项
CryptEncodeObject 对lpszStructType结构进行编码
CyptEncodeObjectEx 对lpszStructType结构进行编码,此函数支持内存分配选项
数据加密/解密函数

这些函数支持数据的加密/解密操作。

CryptEncrypt 和CryptDecrypt要求在被调用前指定一个密钥。这个密钥可以由CryptGenKey、CryptDeriveKey 或CryptImportKey 产生。创建密钥时要指定加密算法。
CryptSetKeyParam函数 可以指定额外的加密参数。
CryptDecrypt 使用指定加密密钥来解密一段密文
CryptEncrypt 使用指定加密密钥来加密一段明文
CryptProtectData 执行对DATA_BLOB结构的加密
CryptUnprotectData 执行对DATA_BLOB结构的完整性验证和解密
哈希和数字签名函数

这些函数在应用程序中完成计算哈希、创建和校验数字签名。

CryptCreateHash创建一个空哈希对象
CryptDestoryHash 销毁一个哈希对象
CryptDuplicateHash 复制一个哈希对象
CryptGetHashParam 得到一个哈希对象参数
CryptHashData 对一块数据进行哈希,把它加到指定的哈希对象中
CryptHashSessionKey 对一个会话密钥进行哈希,把它加到指定的哈希对象中
CryptSetHashParam 设置一个哈希对象的参数
CryptSignHash 对一个哈希对象进行签名
CryptVerifySignature 校验一个数字签名
证书库函数

一个用户站点可以收集许多证书。这些证书是为这个站点的用户所使用的,证书描述了这个用户的具体身份。对于每个人,可能有一个以上的证书。证书库和其相关的函数提供了对库获得、枚举、验证和使用证书库里的信息。

CertAddStoreToCollection在证书库中增加一个证书
CertCloseStore 关闭一个证书库句柄
CertControlStore 如果证书缓冲区和证书本身内容不相符时,允许给应用程序发一个通知
CertDuplicateStore 通过增加引用计数来复制证书库句柄
CertEnumPhysicalStore 对于指定系统库枚举物理库
CertEnumSystemStore 枚举所有可用的系统库
CertEnumSystemStoreLocation 枚举可用系统库的所有位置
CertGetStoreProperty 得到一个库的属性
CertOpenStore 使用指定库类型来打开证书库
CertOpenSystemStore 打开一个系统证书库
CertRegisterPhysicalStore 在一个注册系统库里增加一个物理库
CertRegisterSystemStore 注册一个系统库
CertRemoveStoreFromCollection 从一个库集合里删除证书库
CertSaveStore 保存证书库
CertSetStoreProperty 设置证书属性
CertUnregisterPhysicalStore 从系统库中删除一个物理库
CertUnregisterSystemStore 反注册一个指定系统库

二、pkcs11

(一)学习pkcs #11

PKCS #11标准定义了与密码令牌(如硬件安全模块(HSM)和智能卡)的独立于平台的API,并将API本身命名为“Cryptoki”(“PKCS#11”通常用于指代API以及定义它的标准)。 API定义了最常用的加密对像类型(RSA密钥,X.509证书,DES / 三重DES密钥等)以及使用,创建/生成,修改和删除这些对象所需的所有功能。

pkcs密码中间件位于上层应用和底层安全设备之间,应用基于 PKCS#11 标准接口开发各类应用程序。主要包括2个库:

  • 主API库:提供给应用的PKCS11接口。

  • tokenDLL库:由主API库调用,完成从上向下到指定设备的套接。

  • 安全密码设备:安全服务资源和实施的载体,完成具体安全功能支撑。

PKCS #11模型中重要的概念之一是slot,也称为。一个slot为一个密码设备对象。某个打开的slot会话称之为session。Session之间存在不同的验证权限,而同一个slot的不同的session之间存在操作的互相影响性,同时在某些状况下,权限会发生同步。另外一个重要的概念是对象,PKCS #11中支持几种重要的对象,如公钥、私钥、对称密钥,数据对象等。

PKCS#11创建和支持下列对象:

对象说明
CKO_DATA 应用程序定义的对象。对象的数据结构可由应用程序任意定义,但是数据意义的解释由应用程序负责。
CKO_SECRET_KEY 对称加密算法使用的密钥。
CKO_CERTIFICATE X.509
CKO_PUBLIC_KEY RSA
CKO_PRIVATE_KEY RSA
CKO_MECHANISM 算法对象

PKCS#11的对象可根据其生命期长短的不同分成两大类:一类是持久存储的类对象,这类对象被保存在USB Key的安全存储区域当中,直到应用程序主动删除这些对象;另一类是会话对象,这类对象只存在于运行时建立的特定会话(Session对象)当中,一旦会话结束,这类对象也跟着被删除。

PKCS#11的对象除了生命期长短有分别之外,在访问权限上也有限制。所有的对象都可根据访问权限的不同分成两大类:一类是公开对象,这类对象是任何用户都可以访问的;另一类是私有对象,这一类对象只有身份被验证的用户才有权访问。决定对象的访问限制类型的模板属性是CKA_PRIVATE。

(二)使用方式

PKCS #11的使用流程和步骤如下:

初始化 PKCS #11 库

C_Initialize()语法如下:

C_Initialize(CK_VOID_PTR pInitArgs);

pInitArgs 是空值 NULL_PTR 或是指向 CK_C_INITIALIZE_ARGS 结构的指针。通过 NULL_PTR,该库可以将 Oracle Solaris 互斥锁用作锁定原语,在多个线程之间仲裁对内部共享结构的访问。请注意,Oracle Solaris 加密框架不接受互斥锁。由于 cryptoki 库的此实现可以安全高效地处理多线程,因此建议使用 NULL_PTR。应用程序还可以使用 pInitArgs 来设置诸如 CKF_LIBRARY_CANT_CREATE_OS_THREADS 之类的标志。C_Finalize() 表示应用程序使用 PKCS #11 库结束会话。

可用插槽的列表

C_GetSlotList() 使用的是可用插槽的列表。如果除了 pkcs11_softtoken.so 以外尚未安装任何其他加密提供者,则 C_GetSlotList() 仅返回缺省插槽。C_GetSlotList() 使用以下语法:

C_GetSlotList(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR pSlotList, 
CK_ULONG_PTR pulCount);

如果 tokenPresent 设置为 TRUE,则会将搜索限制在那些存在令牌的插槽。如果 pSlotList 设置为 NULL_PTR,则 C_GetSlotlist() 仅返回插槽的数量。pulCount是指向用于接收插槽计数的位置的指针。

如果 pSlotList 指向用于接收插槽的缓冲区,则 pulCount 将设置为 CK_SLOT_ID 元素的最大预期数量。在返回时,pulCount 将设置为 CK_SLOT_ID 元素的实际数量。

通常,PKCS #11应用程序会调用 C_GetSlotList() 两次。第一次调用 C_GetSlotList() 用于获取进行内存分配的插槽数量,第二次调用 C_GetSlotList() 用于检索插槽。

获取指定令牌

C_GetMechanismList() 用于获取指定令牌所支持的机制类型的列表。语法如下所示:

C_GetMechanismList(CK_SLOT_ID slotID, CK_MECHANISM_TYPE_PTR pMechanismList, 
CK_ULONG_PTR pulCount);

slotID 用于标识令牌的插槽。pulCount 是指向用于接收机制数量的位置的指针。如果 pMechanismList 设置为 NULL_PTR,则 *pulCount 将返回机制的数量。否则,必须将 *pulCount 设置为列表的大小,pMechanismList 必须指向用于存放列表的缓冲区。

(三)函数接口

PKCS #11标准颁发了70余条指令。其中部分指令简介如下表:

接口类型函数名称描述
通用接口函数 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 完成一个多部分摘要操作
签名和消息鉴别函数 C_SignInit 初始化一个签名操作
  C_Sign 签名单部分数据
  C_SignUpdate 继续一个多部分签名操作
  C_SignFinal 完成一个多部分签名操作
  C_SignRecoverInit 初始化一个签名操作,在操作中数据能从签名中恢复
  C_SignRecover 签名单部分数据,在操作中数据能从签名中恢复
签名鉴定消息鉴别函数 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

CSP接口标准为微软所颁发,在windows操作系统上通行。CSP中重要的概念是容器,一个容器中具有一对公私钥。而证书却是这一对密钥的附加属性了。

CSP总共有23个函数接口。简介如下:

接口类型函数名称描述
CSP连接函数 CPAcquireContext 为应用程序创建一个上下文
  CPGetProvParam 返回CSP相关的信息
  CPReleaseContext 释放CPAcquireContext创建的上下文
  CPSetProvParam 设置CSP的参数操作
CSP密钥生成和交换函数 CPDeriveKey 从一个数据散列中生成一个会话密钥,它保证生成的密钥互不相同
  CPDestroyKey 释放一个密钥句柄,释放后,句柄将无效,密钥将无法再被访问
  CPExportKey 从CSP容器中导出密钥
  CPGenKey 用来生成密钥或密钥对
  CPGenRandom 使用随机数填充一个缓冲
  CPGetKeyParam 用来得到加密操作密钥的属性
  CPGetUserKey 用来获取CSP容器中的持久密钥对
  CPImportKey 从一个blob中导入密钥到CSP容器中
  CPSetKeyParam 设置密钥的属性
CSP加解密函数 CPDecrypt 用来解密先前被加密的数据
  CPEncrypt 用来加密明文
CSP散列和数字签名函数 CPCreateHash 初始化并散列输入数据
  CPDestroyHash 删除一个散列对象句柄
  CPDuplicateHash 创建一个散列对象的拷贝
  CPGetHashParam 获取散列对象的计算结果
  CPHashData 散列输入的数据
  CPSetHashParam 定制一个散列对象的属性
  CPSignHash 签名一个散列对象
  CPVerifySignature 校验一个数字签名

国家密码管理局在2011年也制定了相关的接口标准,其标准在上层方面借鉴或参考了CSP接口,存在容器的概念,在底层操作上借鉴或参考了P11接口,有对象的标准,同时支持获取相关属性。

三、GMT 0016-2012

(一)学习GMT 0016-2012

接口规范

GMT 0016-2012是国家规定的智能密码钥匙密码应用接口规范,规定了基于PKI密码体制的智能密码钥匙密码应用接口,描述了密码应用接口的函数、数据类型、参数的定义和设备的安全要求。适用于智能密码钥匙产品的研制、使用和检测。

规范性引用文件

下列文件对于本标准的应用是必不可少的。凡是注日期的引用文件。仅所注日期的版本适用于本文件;凡是不注日期的引用文件,其最新版本(包括所有的修改单)适用于本文件;GM/T 0006密码应用标识规范;GM/T AAAA SM2密码算法使用规范

其中还包括了对部分术语的规定:

应用application包括容器、设备认证密钥和文件的一种结构,具备独立的权限管理。
容器container 密码设备中用于保存密钥所划分的唯一性存储空间。
设备device 本标准中将智能密码钥匙统称为设备。
设备认证device authentication 智能密码钥匙对应用程序的认证。
设备认证密钥device authentication key 用于设备认证的密钥。
设备标签label 设备的别名,可以由用户进行设定并存储于设备内部。
消息鉴别码message authentication code ; MAC 消息鉴别算法的输出。
管理PIN administrator PIN 管理员的口令,为ASCII字符串。
用户PIN user PIN 用户的口令,为ASCII字符串。

(二)使用方式

层次关系

智能密码钥匙密码应用接口位于智能密码钥匙应用程序与设备之间,如下图所示。

应用结构

一个设备中存在设备认证密钥和多个应用,应用之间相互独立。设备的逻辑结构如下图所示。

应用由管理员PIN、用户PIN、文件和容器组成,可以存在多个文件和多个容器。每个应用维护各自的与管理员PIN和用户PIN相关的权限状态。

容器中存放加密密钥对、签名密钥对和会话密钥。其中加密密钥对用于保护会话密钥,签名密钥对用于数字签名和验证,会话密钥用于数据加解密和MAC运算。容器中也可以存放与加密密钥对对应的加密数字证书和与签名密钥对对应的签名数字证书。其中,签名密钥对由内部产生,加密密钥对由外部产生并安全导入,会话密钥可由内部产生或者由外部产生并安全导入。

(三)函数

设备管理系列函数

访问控制系列函数

应用管理系列函数

文件管理系列函数

容器管理系列函数

密码服务系列函数

 

 

四、GMT 0018-2012

(一)学习GMT 0018-2012

接口标准

GMT 0018-2012标准规定了公钥密码基础设施应用技术体系下服务类密码设备的应用接口标准。适用于服务类密码设备的研制、使用,以及基于该类密码设备的应用开发,也可用于指导该类密码设备的检测。

规范性引用文件

下列文件对于本文件的应用是必不可少的。凡是注日期的引用文件,仅注日期的版本适用于本文件;凡是不注日期的引用文件,其最新版本(包括所有的修改单)适用于本文件;GM/T 0006密码应用标识规范;GM/T AAAA SM2密码算法使用规范。

其中还包括了对部分术语的规定:

算法标识algorithm identifier用于对密码算法进行唯一标识的符号。
非对称密码算法/公钥密码算法asymmetric cryptographic algorithm/public key cryptograplrithmts 加解密使用不同密钥的密码算法。
解密decipherment/decryption 加密过程对应的逆过程。
设备密钥device key pair 存储在设备内部的用于设备管理的非对称密钥对,包含签名密钥对和加密密钥对。
加密encipherment/encryption 对数据进行密码变换以产生密文的过程。
密钥加密密钥key encryption key ;KEK 对密钥进行加密保护的密钥。
公钥基础设施public key infrastructure;PKI 用公钥密码技术建立的普遍适用的基础设施,为用户提供证书管理和密钥管理等安全服务。
私钥访问控制码private key access password 用于验证私钥使用权限的口令字。
对称密码技术/对称密码体制symmetric cryptographic technique 原发者和接收者均采用同一秘密密钥进行变换的密码技术(体制)。其中,加密密钥与解密密钥相同,或者一个密钥可以从另一个密钥导出的密码体制。
会话密钥session key 处于层次化密钥结构中的最低层,仅在一次会话中使用的密钥。
用户密钥user key 存储在设备内部的用于应用密码运算的非对称密钥,包含签名密钥对和加密密钥对。

(二)使用方式

在公钥密码基础设施应用技术体系框架中,密码设备服务层由密码机,密码卡,智能密码终端等设备组成,通过本标准规定的密码设备应用接口向通用密码服务层提供基础密码服务。如下图所示。

基础密码服务包括密钥生成、单一的密码运算、文件管理等的服务。

本标准采用C语言描述接口函数。如无特别说明,函数中参数的长度单位均为字节数。

(三) 函数

设备管理类函数

设备管理类函数包括以下具体函数:

A.打开设备:SDF_OpenDevice

B.关闭设备:SDF_CloseDevice

C.创建会话:SDF_OpenSession

D.关闭会话:SDF_CloseSession

E.获取设备信息:SDF_GetDeviceInfo

F.产生随机数:SDF_GenerateRandom

G.获取私钥使用权限:SDF_GetPrivateKeyAccessRight

H.释放私钥使用权限:SDF_ReleasePrivateKeyAccessRight

密钥管理类函数
  • 导出 RSA 签名公钥∶SDF_ExportSignPublicKey_RSA

  • 导出 RSA 加密公钥∶SDF_ExportEncPublicKey_RSA

  • 产生 RSA非对称密钥对并输出∶SDF_GenerateKeyPair_RSA

  • 生成会话密钥并用内部 RSA公钥加密输出∶SDF_GenerateKeyWithIPK_RSA

  • 生成会话密钥并用外部 RSA公钥加密输出∶SDF_GenerateKeyWithEPK_RSA

  • 导入会话密钥并用内部 RSA私钥解密∶SDF_ImportKeyWithISK_RSA

  • 基于 RSA 算法的数字信封转换∶SDF_ExchangeDigitEnvelopeBaseOnRSA

  • 导出 ECC签名公钥∶SDF_ExportSignPublicKey_ECC

  • 导出 ECC 加密公钥∶SDF_ExportEncPublicKey_ECC

  • 产生 ECC非对称密钥对并输出∶SDF_GenerateKeyPair_ECC

  • 生成会话密钥并用内部 ECC公钥加密输出∶SDF_GenerateKeyWithIPK_ECC

  • 生成会话密钥并用外部 ECC公钥加密输出:SDF_GenerateKeyWithEPK ECC

  • 导入会话密钥并用内部 ECC私钥解密∶SDF_ImportKeyWithISK_ECC

  • 生成密钥协商参数并输出;SDF_GenerateAgreementDataWithECC

  • 计算会话密钥∶SDF_GenerateKeyWiuhECC

  • 产生协商数据并计算会话密钥∶SDF_GenerateAgreementDataAndKeyWithECC

  • 基于 ECC算法的数字信封转换∶SDF_ExchangeDigitEnvelopeBaseOnECC

  • 生成会话密钥并用密钥加密密钥加密输出∶SDF_GenerateKeyWithKEK

  • 导入会话密钥并用密钥加密密钥解密∶SDF_ImportKeyWithKEK

  • 销毁会话密钥∶SDF_DestroyKey

非对称算法运算类函数

posted @ 2021-04-24 22:16  20181325张雯  阅读(599)  评论(0编辑  收藏  举报