Crypto API、PKCS#11与GM/T

Crypto API、PKCS#11与GM/T

Microsoft Crypto API

Microsoft 加密技术包括 CryptoAPI、加密服务提供程序 (CSP) 、CryptoAPI 工具、CAPICOM、Wintrust.dll、颁发和管理证书以及开发可自定义的公钥基础结构。 还介绍了证书和智能卡注册、证书管理以及自定义模块开发。

Cryptographic API (CryptoAPI) 是微软在 Windows 操作系统中添加的密码编译机能,作为资料加密与解密功能的重要基础,CryptoAPI 支持同步,异步的密钥加密处理,以及操作系统中的数字证书的管理工作。从Windows NT 4.0引入此功能,并在以后版本的操作系统中不断增强。

目前的 CryptoAPI 支持下列功能:

  • 基础密码学函数,如
    • 内文函数 (Context function)。
    • 密钥产生函数 (Key generation function)。
    • 密钥交换函数 (Key Exchange function)。
  • 证书编码与解码函数(支持散列功能)。
  • 证书存储函数。
  • 简单消息函数。
    • 加密与解密消息与资料。
    • 对消息与资料进行签名。
    • 对收到的消息与相关资料进行数字签名验证的检查。
  • 低端消息函数。

由于 CryptoAPI 使用上过于复杂,因此微软另外为 CryptoAPI 开发更为容易使用的 CAPICOM组件,以及 Data Protection API。从Windows Vista开始,推出了新一代密码学API Cryptography API: Next Generation(简称CNG)。

CryptoAPI的系统体系结构主要由五个功能区域组成:

  1. 基本加密函数
    • 用于连接到 CSP 的 上下文函数。 这些函数使应用程序可以按名称选择特定 CSP,或选择可以提供所需的功能类别的特定 CSP。
    • 用于生成和存储 加密密钥的密钥生成函数。 更改链接模式、初始向量和其他加密功能包含完全支持。
    • 用于交换或传输密钥的 密钥交换功能。
  2. 证书编码/解码函数
    • 用于对数据进行加密或解密的函数。 还包括对哈希数据的支持。
  3. 证书存储函数
    • 用于管理数字证书集合的函数。
  4. 简化消息函数
    • 用于加密和解密消息和数据的函数。
    • 用于对消息和数据进行签名的函数。
    • 用于验证收到的消息和相关数据的签名真实性的函数。
  5. 低级别消息函数
    • 用于执行简化消息函数执行的所有任务的函数。 低级别消息函数比简化消息函数提供的灵活性更高,但需要更多函数调用。

可参照下图:

cryptoapi 体系结构

1.数据哈希(摘要)

在Crypto API中,若要获取哈希值,请使用CryptCreateHash()创建哈希对象。 此对象将累积要验证的数据。 然后,将数据添加到包含CryptHashData()函数的哈希对象。最后一个数据块添加到哈希后,CryptGetHashParam()函数用于获取数据的哈希值。通过在获取哈希值后立即使用CryptDestroyHash()销毁哈希对象,可以提供更好的安全性。

例子(部分代码)如下:

	if (!CryptCreateHash(hCryptProv, hash_type, 0, 0, &hCryptHash))
	{
		DWORD e = GetLastError();
		CString str;
		str.Format("创建哈希句柄出错!错误代码为:%x!", e);
		HandleError(str);
		return false;
	}

	if (!CryptHashData(hCryptHash, (BYTE*)hash_message.GetBuffer(), hash_message.GetLength(), 0))
	{
		int e = GetLastError();
		CString str;
		str.Format("计算哈希值出错!错误代码为:%d!", e);
		HandleError(str);
		return false;
	}

	char hash_data[512];
	DWORD hash_len = 512;
	if (!CryptGetHashParam(hCryptHash, HP_HASHVAL, (BYTE*)hash_data, &hash_len, 0))
	{
		int e = GetLastError();
		CString str;
		str.Format("获取哈希值出错!错误代码为:%d!", e);
		HandleError(str);
		return false;
	}

	char hash_hex[512];
	for (unsigned int i = 0; i <= hash_len - 1; i++)
	{
		int hash_bit = hash_data[i];
		int first = (hash_bit & 0xf0) >> 4;
		int second = hash_bit & 0x0f;
		char tmp[2];
		_itoa(first, tmp, 16);
		hash_hex[i * 2] = tmp[0];
		_itoa(second, tmp, 16);
		hash_hex[i * 2 + 1] = tmp[0];
	}

	hash_hex[hash_len * 2] = '\0';
	hash_result.Format("%s", hash_hex);
	CryptDestroyHash(hCryptHash);
	CryptReleaseContext(hCryptProv, NULL);

例子中的hCryptProv为CSP容器,使用CryptAcquireContext()创建

2.加解密

2.1 加密

加密消息

  1. 使用CryptGenKey()函数生成会话密钥。

    进行此调用会生成一个随机密钥并返回一个句柄,以便可以使用该密钥来加密和解密数据。 此时还指定了要使用的加密算法。 因为CryptoAPI 不允许应用程序使用公钥算法来加密大容量数据,所以请使用CryptGenKey()调用指定一个对称算法,如 RC2 或 RC4。

  2. 或者,使用CryptDeriveKey()函数将密码转换为适用于加密的密钥。

    如果应用程序需要对消息进行加密,使具有指定密码的任何人都可以对数据进行解密,请使用CryptDeriveKey()将密码转换为适用于加密的密钥。

  3. 如有必要,使用CryptSetKeyParam()函数设置密钥的额外加密属性

    生成密钥后,可以通过CryptSetKeyParam()函数设置密钥的额外加密属性。 此函数允许使用不同的密钥 salts 对文件的不同部分进行加密,并提供一种方法来更改密钥的密码模式或初始化向量。 这些参数可用于使加密符合特定的数据加密标准。

  4. CryptEncrypt()函数加密文件中的数据。

    CryptEncrypt()函数采用上一步中生成的会话密钥,并对数据缓冲区进行加密。

  5. (可选)使用CryptExportKey() 函数允许当前用户在将来解密数据。

    为了允许当前用户在将来对数据进行解密,CryptExportKey()函数用于将解密密钥以加密形式保存 (密钥 BLOB) ,只能使用用户的私钥对其进行解密。 此函数需要用户的密钥交换公钥以实现此目的,可以使用CryptGetUserKey() 函数获取此公钥。CryptExportKey()函数将返回一个必须由应用程序存储的密钥 BLOB,以便在对文件进行解密时使用

2.2 解密

使用CryptDecryptMessage()解密数据

  1. 获取指向已加密的 BLOB 的指针。
  2. 打开证书存储区。
  3. 创建证书存储区数组。
  4. 初始化CRYPT_DECRYPT_MESSAGE_PARA结构。
  5. 调用CryptDecryptMessage()以对消息中包含的数据进行解密。

具体加解密代码如下(省略挂库部分):

#define ENCRYPT_ALGORITHM CALG_RC4 
//规定使用RC4对称加密算法
//下面函数通过参数mode实现了加解密二合一
BOOL MyEncorDec(
                        PCHAR szSource, 
                        PCHAR szDestination, 
                        PCHAR szPassword,
                        BOOL mode)
                        //--------------------------------------------------------------------
                        //   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.
                        //     mode : Encrypt(0) or Decrypt(1)
{ 
    //--------------------------------------------------------------------
    //   Declare and initialize local variables.
    
    FILE *hSource; 
    FILE *hDestination; 
    
    HCRYPTPROV hCryptProv; 
    HCRYPTKEY hKey; 
    HCRYPTHASH hHash; 
        
    PBYTE pbBuffer; 
    DWORD dwBlockLen; 
    DWORD dwBufferLen; 
    DWORD dwCount; 
    
    BYTE FILEHASH[16]={0};//md5长度16字节(128位)
    //--------------------------------------------------------------------
    // 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表示使用默认密钥容器,默认密钥容器名
//为用户登陆名
        NULL, 
        PROV_RSA_FULL, 
        0))
    {
        printf("A cryptographic provider has been acquired. \n");
    }
    else
    {
        if(CryptAcquireContext(
            &hCryptProv, 
            NULL, 
            NULL, 
            PROV_RSA_FULL, 
            CRYPT_NEWKEYSET))//创建密钥容器
        {
            //创建密钥容器成功,并得到CSP句柄
            printf("A new key container has been created.\n");
        }
        else
        {
            HandleError("Could not create a new key container.\n");
        }
        
    }

    //--------------------------------------------------------------------
    // 创建一个会话密钥(session key)
    // 会话密钥也叫对称密钥,用于对称加密算法。
    // (注: 一个Session是指从调用函数CryptAcquireContext到调用函数
    //   CryptReleaseContext 期间的阶段。会话密钥只能存在于一个会话过程)

    //--------------------------------------------------------------------
    // 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");
    }  
    //--------------------------------------------------------------------
    // 计算输入文件的散列
    /* 获取文件大小 */
    fseek (hSource , 0 , SEEK_END);
    long long lSize = ftell (hSource);
    rewind (hSource);

    /* 分配内存存储整个文件 */
    char *SourceFile = (char*) malloc (sizeof(char)*lSize);
    if(SourceFile==NULL){
        HandleError("Error during Input File Hash-malloc!\n");

    }
    long long result = fread (SourceFile,1,lSize,hSource);
    if(result!=lSize){
        HandleError("Error during Input File Hash-fread!\n");
    }

    if(CryptHashData(
        hHash, 
        (BYTE *)SourceFile, 
        lSize, 
        0))
    {
        printf("Input File Hash Success\n");
    }
    else
    {
        HandleError("Error during Input File Hash-hashing. \n"); 
    }
    DWORD FileHashLen=16;
    if(CryptGetHashParam(
        hHash,
        HP_HASHVAL,
        FILEHASH,
        &FileHashLen,
        0))
    {
        printf("MD5:\t");
        for(DWORD i=0;i<FileHashLen;i++)         //输出md5值
		{
			printf("%02x\n",FILEHASH[i]);
		}
        printf("\n");
    }else
    {
        HandleError("Error during Input File Hash-length. \n"); 
    }

    if(SourceFile!=NULL){
        free(SourceFile);
    }
    rewind (hSource);
    CryptDestroyHash(hHash); 
    hHash = 0; 
    //--------------------------------------------------------------------
    // 用输入的密码产生一个散列
    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"); 
    }
    //--------------------------------------------------------------------
    // Destroy the hash object. 
    
    CryptDestroyHash(hHash); 
    hHash = 0; 
    
    //--------------------------------------------------------------------
    //  The session key is now ready. 
    
    //--------------------------------------------------------------------
    // 因为加密算法是按ENCRYPT_BLOCK_SIZE 大小的块加密的,所以被加密的
// 数据长度必须是ENCRYPT_BLOCK_SIZE 的整数倍。下面计算一次加密的
// 数据长度。

    dwBlockLen = 1000 - 1000 % ENCRYPT_BLOCK_SIZE; 
    
    //--------------------------------------------------------------------
    // Determine the block size. If a block cipher is used, 
    // it must have room for an extra block. 
    
    if(ENCRYPT_BLOCK_SIZE > 1) 
        dwBufferLen = dwBlockLen + ENCRYPT_BLOCK_SIZE; 
    else 
        dwBufferLen = dwBlockLen; 
    
    //--------------------------------------------------------------------
    // Allocate memory. 
    if(pbBuffer = (BYTE *)malloc(dwBufferLen))
    {
        printf("Memory has been allocated for the buffer. \n");
    }
    else
    { 
        HandleError("Out of memory. \n"); 
    }
    //--------------------------------------------------------------------
    // In a do loop, encrypt the source file and write to the source file. 
    
    do 
    { 
        
        //--------------------------------------------------------------------
        // Read up to dwBlockLen bytes from the source file. 
        dwCount = fread(pbBuffer, 1, dwBlockLen, hSource); 
        if(ferror(hSource))
        { 
            HandleError("Error reading plaintext!\n");
        }

        //--------------------------------------------------------------------
        // 加\解密密数据
        if(!((mode==1)?
        CryptDecrypt(
            hKey,            //密钥
            0,                //如果数据同时进行散列和加密,这里传入一个
//散列对象
            feof(hSource),    //如果是最后一个被加密的块,输入TRUE.如果不是输.
                            //入FALSE这里通过判断是否到文件尾来决定是否为
//最后一块。
            0,                //保留
            pbBuffer,        //输入被加密数据,输出加密后的数据
            &dwCount)
        :CryptEncrypt(
            hKey,            //密钥
            0,                //如果数据同时进行散列和加密,这里传入一个
//散列对象
            feof(hSource),    //如果是最后一个被加密的块,输入TRUE.如果不是输.
                            //入FALSE这里通过判断是否到文件尾来决定是否为
//最后一块。
            0,                //保留
            pbBuffer,        //输入被加密数据,输出加密后的数据
            &dwCount,        //输入被加密数据实际长度,输出加密后数据长度
            dwBufferLen)    //pbBuffer的大小。
        ))        //输入被加密数据实际长度,输出加密后数据长度
        { 
            HandleError("Error during CryptDecrypt/CryptEncrypt. \n"); 
        } 
        
        //--------------------------------------------------------------------
        // Write data to the destination file. 
        
        fwrite(pbBuffer, 1, dwCount, hDestination); 
        if(ferror(hDestination))
        { 
            HandleError("Error writing ciphertext.");
        }
        
    }while(!feof(hSource)); 
    //--------------------------------------------------------------------
    //  End the do loop when the last block of the source file has been
    //  read, encrypted, and written to the destination file.
    
    //--------------------------------------------------------------------
    // Close files.
    
    if(hSource) 
        fclose(hSource); 
    if(hDestination) 
        fclose(hDestination); 
    
    //--------------------------------------------------------------------
    // Free memory. 
    
    if(pbBuffer) 
        free(pbBuffer); 
    
    //--------------------------------------------------------------------
    // Destroy session key. 
    
    if(hKey) 
        CryptDestroyKey(hKey); 
    
    //--------------------------------------------------------------------
    // Destroy hash object. 
    
    if(hHash) 
        CryptDestroyHash(hHash); 
    
    //--------------------------------------------------------------------
    // Release provider handle. 
    
    if(hCryptProv) 
        CryptReleaseContext(hCryptProv, 0);
    return(TRUE); 
}

PKCS#11

PKCS即公钥密码标准(Public Key Cryptography Standards),它是由美国RSA实验室与遍布全球的安全系统开发者一起合作指定的一组规范,以推动公钥密码的发展。最早发布的PKCS文档是早期一群公钥技术使用者在1991年召开的一次会议的成果。目前PKCS规范早已被广泛的应用和实施,部分PKCS规范已经成为多个国际组织正式或事实上的标准

其中,PKCS #11 v2.2 定义了与密码令牌(如硬件安全模块(HSM)和智能卡)的独立于平台的API。主要内容包括:

  1. 通用数据类型(General Data Types)
  2. 对象 (Objects)
  3. 函数(Functions)
  4. 机制(Mechanisms)

大多数商业认证机构软件使用PKCS#11访问CA签名密钥或注册用户证书。需要使用智能卡的跨平台软件使用PKCS#11,如Openssl(扩展)和Firefox等。

PKCS #11 标准定义了公用的界面用于证书和加密密钥的创建、使用和管理。此界面的每一项实施都为其下面的技术提供一个具体的途径,因为 PKCS #11 没有关于实现核心功能体加密令牌的声明。目前市场上的解决方案都是基于软件、以及智能卡或专门的硬件加密模块。每个 PKCS #11 兼容库都使用自己的方法来包含此类专门设备以及使用这些设备产生和处理加密相关的数据。

由于 PKCS #11 定义了一个独立于平台的界面,因此有很大范围的制造商推出了多种不同的解决方案,并且标准能够在很多的平台和操作系统中得到支持。

PKCS #11 兼容库通过一定制作精美的界面来提供他们的功能。根据实施中的主要目标的不同,一个 PKCS #11 库可能仅支持所定义界面的一个子集。

要建立一个 PKI,应用程序使用一个 PKCS #11 模块需要访问一个持久的存储器,该存储器为用户证书和私人密钥提供一个安全可靠的数据存储空间。PKCS #11 没有关于此存储机制的声明。作为一种共用机制,已经证实对于提供所要求的功能,目录服务是一个有很有用的途径。对此类目录服务的访问常常通过使用小型目录访问协议(LDAP)来实现。

基于 PKCS #11 的应用程序仍然要实现所有的管理工作,从而提供处理 PKCS #11 功能体所需的数据。

应用程序的开发商可以通过使用不同的 PKCS #11 实现模块,来充分利用基于 public key 安全机制的完整功能的优点,无需对运行的平台或软件系统做任何的改动。而且,公司还能够使用在整个机构都相同的工具和策略来管理他们的环境和应用程序。

要让其他用户能够阅读加密的信息或者验证签名的电子邮件,用户证书必须要存储在一个公共目录内。此目录通常位于服务器上,有关的机构单位都能够访问该服务器。

GM/T 0016-2012

国标中关于智能密码钥匙密码的应用接口规范。标准规定了基于 PKI 密码体制的智能密码钥匙密码应用接口, 描述了密码应用接口的函数、数据类型、参数的定义和设备的安全要求,部分数据结构和接口定义如下:

#ifndef __SDF_H
#define __SDF_H

typedef struct Devicelnfo_st {
	unsigned char IssuerName[40];
	//设备生产厂商名称
	unsigned char DeviceName[16];
	//设备型号
	unsigned char DeviceSerial[16];
	//设备编号:包含: 日期(8 字符) 、 批次号(3 字符) 、 流水号	(5 字符)
	unsigned int DeviceVersion;
	//设备内部软件的版本号
	unsigned int StandardVersion;
	//密码设备支持的接口规范版本号
	unsigned int AsymAlgAbility[2];
	//前 4 字节表示支持的算法,表示方法为非对称算法标识按位或的结果;
	//后 4 字节表示算法的最大模长,表示方法为支持的模长按位或的结果
	unsigned int SymAlgAbility;
	//所有支持的对称算法, 表示方法为对称算法标识按位或运算结果
	unsigned int HashAlgAbility;
	//所有支持的杂凑算法, 表示方法为杂凑算法标识按位或运算结果
	unsigned int BufferSize;
	//支持的最大文件存储空间(单位字节)
}DEVICEINFO;

# define ECCref_MAX_BITS 512
# define ECCref_MAX_LEN ((ECCref_MAX_BITS + 7) / 8)
typedef struct ECCrefPublicKey_st {//ECC 公钥数据结构
	unsigned int bits;
	//密钥位长
	unsigned char x[ECCref_MAX_LEN];
	//公钥 x 坐标
	unsigned char y[ECCref_MAX_LEN];
	//公钥 y 坐标
} ECCrefPublicKey;

typedef struct ECCrefPrivateKey_st {//ECC 私钥数据结构
	unsigned int bits;
	//密钥位长
	unsigned char K[ECCref_MAX_LEN];
	//私钥
} ECCrefPrivateKey;

typedef struct ECCCipher_st {//ECC 加密数据结构
	unsigned char x[ECCref_MAX_LEN];
	//X 分量
	unsigned char y[ECCref_MAX_LEN];
	//Y 分量
	unsigned char M[32];
	//明文的杂凑值
	unsigned int L;
	//密文数据长度
	unsigned char C[L];
	//密文数据
} ECCCipher;

typedef struct ECCSignature_st
{
	unsigned char r[ECCref_MAX_LEN];
	//签名的 r 部分
	unsigned char s[ECCref_MAX_LEN];
	//签名的 s 部分
} ECCSignature;

typedef struct SDF_ENVELOPEDKEYBLOB {
	unsigned long ulAsymmAlglD;
	//保护对称密钥的非对称算法标识
	unsigned long ulSymmAlgID;
	//对称算法标识(必须为 ECB 模式)
	ECCCIPHERBLOB ECCCipherBlob;
	//对称密钥密文
	ECCPUBLICKEYBLOB PubKey;
	//加密密钥对的公钥
	unsigned char cbEncryptedPriKey[64];
	//加密密钥对的私钥密文,有效长度为原文的(ulBits + 7)/8
	//私钥原文为ECCPRIVATEKEYBLOB结构中的PrivateKey
}ENVELOPEDKEYBLOB, *PENVELOPEDKEYBLOB;

int SDF_OpenDevice(void ** phDeviceHandle);
/*
描述:打开密码设备
参数:phDeviceHandle
*/

#endif // !__SDF_H

posted @ 2021-04-26 22:53  Wardcore  阅读(987)  评论(0)    收藏  举报