Cryptography API: Next Generation(CNG)使用梳理——对称加密算法应用

对称加密算法,顾名思义就是加密与解密使用相同的密钥

CNG支持的对称加密算法有_3DES, _3DES_112, AES, AES_CMAC, AES_GMAC, DES, DESX, RC2, RC4, XTS_AES

以AES为例,简单叙述一下,它的基本使用过程,设置操作模式,加密填充方式,密钥导入导出,及添加身份验证

一、一般流程:

1、获取算法提供者句柄

2、设置操作模式(全局)

3、获取密钥对象大小

4、为密钥对象分配空间

5、获取加密块大小

6、创建对称加密密钥

7、获取加密后内容大小

8、为加密后内容分配空间

9、加密内容(设置填充方式)

10、导出密钥

11、销毁原密钥

12、导入密钥

13、获取解密后内容大小

14、为解密内容分配空间

15、解密内容(设置和加密时一样的填充方式)

其中和Hash一样,在Win 7以后3、4步骤可以不用操作,CNG内部会自行管理密钥对象

所需要函数

//打开支持所需算法的算法提供者句柄
NTSTATUS BCryptOpenAlgorithmProvider(
  [out] BCRYPT_ALG_HANDLE *phAlgorithm,
  [in]  LPCWSTR           pszAlgId,
  [in]  LPCWSTR           pszImplementation,
  [in]  ULONG             dwFlags
);
//获取密钥对象的大小
NTSTATUS BCryptGetProperty(
  [in]  BCRYPT_HANDLE hObject,
  [in]  LPCWSTR       pszProperty,
  [out] PUCHAR        pbOutput,
  [in]  ULONG         cbOutput,
  [out] ULONG         *pcbResult,
  [in]  ULONG         dwFlags
);
//设置操作模式,可以用于算法提供者句柄,也可以在调用BCryptGenerateSymmetricKey后作用于密钥
NTSTATUS BCryptSetProperty(
  [in, out] BCRYPT_HANDLE hObject,
  [in]      LPCWSTR       pszProperty,
  [in]      PUCHAR        pbInput,
  [in]      ULONG         cbInput,
  [in]      ULONG         dwFlags
);
//创建对称密钥
NTSTATUS BCryptGenerateSymmetricKey(
  [in, out]       BCRYPT_ALG_HANDLE hAlgorithm,
  [out]           BCRYPT_KEY_HANDLE *phKey,
  [out, optional] PUCHAR            pbKeyObject,
  [in]            ULONG             cbKeyObject,
  [in]            PUCHAR            pbSecret,
  [in]            ULONG             cbSecret,
  [in]            ULONG             dwFlags
);
//获取加密内容大小及加密
NTSTATUS BCryptEncrypt(
  [in, out]           BCRYPT_KEY_HANDLE hKey,
  [in]                PUCHAR            pbInput,
  [in]                ULONG             cbInput,
  [in, optional]      VOID              *pPaddingInfo,
  [in, out, optional] PUCHAR            pbIV,
  [in]                ULONG             cbIV,
  [out, optional]     PUCHAR            pbOutput,
  [in]                ULONG             cbOutput,
  [out]               ULONG             *pcbResult,
  [in]                ULONG             dwFlags
);
//导出密钥
NTSTATUS BCryptExportKey(
  [in]  BCRYPT_KEY_HANDLE hKey,
  [in]  BCRYPT_KEY_HANDLE hExportKey,
  [in]  LPCWSTR           pszBlobType,
  [out] PUCHAR            pbOutput,
  [in]  ULONG             cbOutput,
  [out] ULONG             *pcbResult,
  [in]  ULONG             dwFlags
);
//销毁密钥
NTSTATUS BCryptDestroyKey(
  [in, out] BCRYPT_KEY_HANDLE hKey
);
//导入密钥
NTSTATUS BCryptImportKey(
  [in]            BCRYPT_ALG_HANDLE hAlgorithm,
  [in, optional]  BCRYPT_KEY_HANDLE hImportKey,
  [in]            LPCWSTR           pszBlobType,
  [out]           BCRYPT_KEY_HANDLE *phKey,
  [out, optional] PUCHAR            pbKeyObject,
  [in]            ULONG             cbKeyObject,
  [in]            PUCHAR            pbInput,
  [in]            ULONG             cbInput,
  [in]            ULONG             dwFlags
);
//获取解密内容大小及解密
NTSTATUS BCryptDecrypt(
  [in, out]           BCRYPT_KEY_HANDLE hKey,
  [in]                PUCHAR            pbInput,
  [in]                ULONG             cbInput,
  [in, optional]      VOID              *pPaddingInfo,
  [in, out, optional] PUCHAR            pbIV,
  [in]                ULONG             cbIV,
  [out, optional]     PUCHAR            pbOutput,
  [in]                ULONG             cbOutput,
  [out]               ULONG             *pcbResult,
  [in]                ULONG             dwFlags
);
//关闭算法提供者句柄
NTSTATUS BCryptCloseAlgorithmProvider(
  [in, out] BCRYPT_ALG_HANDLE hAlgorithm,
  [in]      ULONG             dwFlags
);

具体流程演示,(代码依旧是根据官方演示案例修改而来,用于演示流程,因此去掉了返回校验过程)

#include 
#include 
#include 

#define STATUS_UNSUCCESSFUL         ((NTSTATUS)0xC0000001L)


#define DATA_TO_ENCRYPT  "Test Data"


const BYTE rgbPlaintext[] = 
{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

static const BYTE rgbIV[] =
{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

static const BYTE rgbAES128Key[] =
{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

int main()
{

    BCRYPT_ALG_HANDLE       hAesAlg                     = NULL;
    BCRYPT_KEY_HANDLE       hKey                        = NULL;
    NTSTATUS                status                      = STATUS_UNSUCCESSFUL;
    DWORD                   cbCipherText                = 0, 
                            cbPlainText                 = 0,
                            cbData                      = 0, 
                            cbKeyObject                 = 0,
                            cbBlockLen                  = 0,
                            cbBlob                      = 0;
    PBYTE                   pbCipherText                = NULL,
                            pbPlainText                 = NULL,
                            pbKeyObject                 = NULL,
                            pbIV                        = NULL,
                            pbBlob                      = NULL;

    // 获取算法提供者句柄
    BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, NULL, 0);

    // 设置操作模式(全局),作用于算法提供者句柄之上,则用此句柄生成的所有密钥都会有效
    BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0);

    // 获取密钥对象大小,Win 7 后此两步可以不做,完全交由CNG内部管理
    BCryptGetProperty(hAesAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbKeyObject, sizeof(DWORD), &cbData, 0);

    // 为密钥对象分配空间
    pbKeyObject = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbKeyObject);

    // 获取加密块大小,可用于确定IV(加密向量)大小、无填充时加密内容大小,此处用于确定向量
    BCryptGetProperty(hAesAlg, BCRYPT_BLOCK_LENGTH, (PBYTE)&cbBlockLen, sizeof(DWORD), &cbData, 0);

    // 为向量创建空间,这两步非必要,可以不做,在加密时对应参数设置为NULL和0即可
    // 如果设置了,那解密时也一定要设置,而且内容必须相同
    pbIV= (PBYTE) HeapAlloc (GetProcessHeap (), 0, cbBlockLen);

    // 初始化(填充)向量
    memcpy(pbIV, rgbIV, cbBlockLen);

    // 创建密钥,密钥的大小,由输入的pbSecret决定
    // 需要确认加密算法所支持的密钥大小,可以通过BCryptGetProperty获取BCRYPT_KEY_LENGTHS属性内容
    // BCRYPT_KEY_LENGTHS_STRUCT keyLengths;
    // BCryptGetProperty(hAesAlg, BCRYPT_KEY_LENGTHS, (PBYTE)&keyLengths, sizeof(BCRYPT_KEY_LENGTHS_STRUCT), &cbData, 0);
    // 密钥大小范围,dwMinLength最小值, dwMaxLength最大值,dwIncrement递增值,单位bits
    // Win 7 后pbKeyObject可以不做,完全交由CNG内部管理
    // BCryptGenerateSymmetricKey(hAesAlg, &hKey, NULL, 0, (PBYTE)rgbAES128Key, sizeof(rgbAES128Key), 0);
    BCryptGenerateSymmetricKey(hAesAlg, &hKey, pbKeyObject, cbKeyObject, (PBYTE)rgbAES128Key, sizeof(rgbAES128Key), 0);

    // 复制明文
    cbPlainText = sizeof(rgbPlaintext);
    pbPlainText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbPlainText);
    memcpy(pbPlainText, rgbPlaintext, sizeof(rgbPlaintext));
    
    // 获取密文所需空间,填充方式为BCRYPT_BLOCK_PADDING,如果设为0,则明文大小必须是或者的密块大小(cbBlockLen)的整数倍
    // 如果不是向量可以
    // BCryptEncrypt(hKey, pbPlainText, cbPlainText, NULL, NULL, 0, NULL, 0, &cbCipherText, BCRYPT_BLOCK_PADDING);
    BCryptEncrypt(hKey, pbPlainText, cbPlainText, NULL, pbIV, cbBlockLen, NULL, 0, &cbCipherText, BCRYPT_BLOCK_PADDING);

    // 分配密文所需空间
    pbCipherText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbCipherText);

    // 加密数据(填充方式为BCRYPT_BLOCK_PADDING,使用向量)
    BCryptEncrypt(hKey, pbPlainText, cbPlainText, NULL, pbIV, cbBlockLen, pbCipherText, cbCipherText, &cbData, BCRYPT_BLOCK_PADDING);

    // 获取导出密钥所需大小,此处使用BCRYPT_KEY_DATA_BLOB而非官方例子中的BCRYPT_OPAQUE_KEY_BLOB
    // 官方例子中的BCRYPT_OPAQUE_KEY_BLOB为非透明的数据,实际上就是实体密钥的内存映射,而后密钥本身
    // 只能在同进程或者跨进程,且CSP相同的情况下使用
    // 此处第二个参数hExportKey,为另个AES句柄,如果设置此值,并将导出类型设置为BCRYPT_AES_WRAP_KEY_BLOB
    // 则导出的密钥会被此hExportKey加密,导入是同样需要解密
    // BCryptExportKey(hKey, hExportKey, BCRYPT_AES_WRAP_KEY_BLOB, NULL, 0, &cbBlob, 0);
    BCryptExportKey(hKey, NULL, BCRYPT_KEY_DATA_BLOB, NULL, 0, &cbBlob, 0);

    // 为导出密钥分配空间
    pbBlob = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbBlob);

    // 导出密钥
    BCryptExportKey(hKey, NULL, BCRYPT_KEY_DATA_BLOB, pbBlob, cbBlob, &cbBlob, 0);
 
    // 销毁当前密钥
    BCryptDestroyKey(hKey);  
    hKey = 0;

    // 清理原先复制的明文空间
    HeapFree(GetProcessHeap(), 0, pbPlainText);
    pbPlainText = NULL;

    // 对密钥对象清零,如果BCryptGenerateSymmetricKey没有使用,此处也可以不用,Win 7后密钥对象可以直接交由CNG内部管理
    memset(pbKeyObject, 0 , cbKeyObject);

    // 重新初始化向量,向量在加密是会改变,所以解密时需要重新初始化
    memcpy(pbIV, rgbIV, cbBlockLen);

    // 导入密钥,需要设置与到导出一样的类型,如:BCRYPT_KEY_DATA_BLOB
    // 同样Win7后可以不用pbKeyObject
    // BCryptImportKey(hAesAlg, NULL, BCRYPT_KEY_DATA_BLOB, &hKey, NULL, 0, pbBlob, cbBlob, 0);
    BCryptImportKey(hAesAlg, NULL, BCRYPT_KEY_DATA_BLOB, &hKey, pbKeyObject, cbKeyObject, pbBlob, cbBlob, 0);

    // 获取解密后的明文大小,需要设置与加密时一样的填充方式和向量值
    BCryptDecrypt(hKey, pbCipherText, cbCipherText, NULL, pbIV, cbBlockLen, NULL, 0, &cbPlainText, BCRYPT_BLOCK_PADDING);

    // 分配明文所需空间
    pbPlainText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbPlainText);

    // 解密
    BCryptDecrypt(hKey, pbCipherText, cbCipherText, NULL, pbIV, cbBlockLen, pbPlainText, cbPlainText, &cbPlainText, BCRYPT_BLOCK_PADDING);

    // 清理
    BCryptCloseAlgorithmProvider(hAesAlg,0);

    BCryptDestroyKey(hKey);

    HeapFree(GetProcessHeap(), 0, pbCipherText);

    HeapFree(GetProcessHeap(), 0, pbPlainText);

    HeapFree(GetProcessHeap(), 0, pbKeyObject);

    HeapFree(GetProcessHeap(), 0, pbIV);

    return 0;
}

注意:

1、设置操作模式分为全局和局部,在算法提供者句柄上设置,则使用此句柄的所有算法都将被设置,而在创建密钥后对密钥句柄设置,则只会改变当前密钥

2、数据填充方式,只有不填充和BCRYPT_BLOCK_PADDING填充,前者需要自己控制明文的大小,使其是密钥支持加密块大小的整数倍

3、密钥导入导出时,如果是在同一台机器上推荐使用BCRYPT_OPAQUE_KEY_BLOB,但其导出的密钥只是实际密钥的内存映射,安全但限制较多。其它则需要BCRYPT_KEY_DATA_BLOB,或者使用BCRYPT_AES_WRAP_KEY_BLOB为密钥再加一次密,如果需要网络传输,那最好直接使用通过共享密钥协议生成的密钥

二、添加身份验证

添加身份认证,具体流程和函数和一般的加密类似,只是操作模式必须是CCM 或者 GCM,而后在加密时添加BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO结构对象参数

实例如下:(演示一个需要分批次解密并认证的例子)

#include 
#include 

const BYTE rgbPlaintext[] = 
{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 
    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
};

static const BYTE rgbNonce[] =
{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

static const BYTE rgbAES128Key[] =
{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

int _tmain(int argc, _TCHAR* argv[])
{
    BCRYPT_ALG_HANDLE       hAesAlg                     = NULL;
    BCRYPT_KEY_HANDLE       hKey                        = NULL;
    DWORD                   cbCipherText                = 0, 
                            cbPlainText                 = 0,
                            cbData                      = 0, 
                            cbBlockLen                  = 0,
                            cdPartSize                  = 0;
    PBYTE                   pbCipherText                = NULL,
                            pbPlainText                 = NULL,
                            pbIV                        = NULL,
                            pbNonce                     = NULL,
                            pAuthTag                    = NULL,
                            pMacContext                 = NULL;

    // 获取算法提供者句柄
    BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, NULL, 0);

    // 设置操作模式(全局)
    BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);

    // 获取支持身份验证标签大小范围
    BCRYPT_AUTH_TAG_LENGTHS_STRUCT authTagLengths;
    BCryptGetProperty(hAesAlg, BCRYPT_AUTH_TAG_LENGTH, (PBYTE)&authTagLengths, sizeof(authTagLengths), &cbData, 0);

    // 获取加密块大小
    BCryptGetProperty(hAesAlg, BCRYPT_BLOCK_LENGTH, (PBYTE)&cbBlockLen, sizeof(DWORD), &cbData, 0);

    // 创建密钥,
    BCryptGenerateSymmetricKey(hAesAlg, &hKey, NULL, 0, (PBYTE)rgbAES128Key, sizeof(rgbAES128Key), 0);

    // 复制明文
    cbPlainText = sizeof(rgbPlaintext);
    pbPlainText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbPlainText);
    memcpy(pbPlainText, rgbPlaintext, sizeof(rgbPlaintext));

    // GCM 的 Nonde 在 NIST 规范中就是12字节,也就是96位的
    // CCM的就有点复杂了,其长度为15-L,L是长度域,范围是2-8,OpenSSL默认是8,也就是Nonde是7
    // 长度域就是明文信息长度需要有多少字节来保存,比如在OpenSSL中
    // 明文信息m,其长度l(m)的范围为0≤l(m)<2^(8L),目的是其长度确保可以由L字节的长度域保存
    // 也就是在OpenSSL中默认由8个字节64位表示长度
    const DWORD GCM_NONCE_SIZE = 12;

    // 此处没有使用随机函数,只是模拟了一个截取随机数的方法
    pbNonce = (PBYTE)HeapAlloc (GetProcessHeap (), 0, GCM_NONCE_SIZE);
    memcpy(pbNonce, rgbNonce, GCM_NONCE_SIZE);

    // 为身份验证标签分配空间
    pAuthTag = (PBYTE)HeapAlloc (GetProcessHeap (), 0, authTagLengths.dwMinLength);
    memset(pAuthTag, 0, authTagLengths.dwMinLength);

    // 填充结构
    BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
    BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
    authInfo.pbNonce = pbNonce;			          //随机数
    authInfo.cbNonce = GCM_NONCE_SIZE;            //随机数大小
    authInfo.pbTag   = pAuthTag;
    authInfo.cbTag   = authTagLengths.dwMinLength;

    // 获取密文所需空间,填充方式必须为0
    BCryptEncrypt(hKey, pbPlainText, cbPlainText, &authInfo, NULL, 0, NULL, 0, &cbCipherText, 0);

    // 分配密文所需空间
    pbCipherText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbCipherText);

    // 加密数据
    BCryptEncrypt(hKey, pbPlainText, cbPlainText, &authInfo, NULL, 0, pbCipherText, cbCipherText, &cbData, 0);

    // 清除明文
    memset(pbPlainText, 0, cbPlainText);

    // 分两次解密
    partSize = cbCipherText / 2;

    // 分配MAC(消息认证码)空间,其大小必须是身份验证标签的最大值dwMaxLength
    pMacContext = (PBYTE)HeapAlloc (GetProcessHeap (), 0, authTagLengths.dwMaxLength);

    // 重新填充结构
    BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
    authInfo.pbNonce      = pbNonce;
    authInfo.cbNonce      = GCM_NONCE_SIZE;
    authInfo.pbTag        = pAuthTag;
    authInfo.cbTag        = authTagLengths.dwMinLength;
    authInfo.pbMacContext = pMacContext;
    authInfo.cbMacContext = authTagLengths.dwMaxLength;

    // 连续解密时需要添加BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG标志,MSDN上说是BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG,但实测好像不是
    authInfo.dwFlags = BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;

    // 连接加密或者解密时,需要保存内部的初始化向量值
    pbIV = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbBlockLen);

    // 第一次解密,初始化向量值第一次调用是会被忽略,只是用作保存
    BCryptDecrypt(hKey, pbCipherText, partSize, &authInfo, pbIV, cbBlockLen, pbPlainText, partSize, &cbData, 0);

    // 解密第二部分
    // 在最后一次解密时清除BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG
    authInfo.dwFlags &= ~BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;

    BCryptDecrypt(hKey, pbCipherText + partSize, partSize, &authInfo, pbIV, cbBlockLen, pbPlainText + partSize, partSize, &cbData, 0);

    // 清理
    BCryptDestroyKey(hKey);
    BCryptCloseAlgorithmProvider(hAesAlg, 0);

    HeapFree(GetProcessHeap(), 0, pbCipherText);
    HeapFree(GetProcessHeap(), 0, pbPlainText);
    HeapFree(GetProcessHeap(), 0, pbNonce);
    HeapFree(GetProcessHeap(), 0, pAuthTag);
    HeapFree(GetProcessHeap(), 0, pMacContext);

    return 0;
}

注意:

1、使用带身份认证的加密时,操作模式必须是CCM 或者 GCM

2、加密和解密的填充方式必须是无填充,dwFlags必须设置为0

3、cbMacContext的大小必须是身份验证标签的最大值dwMaxLength

4、BCRYPT_INIT_AUTH_MODE_INFO结构中的dwFlags值,再最后一次加密或者解密前必须设置为BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG标志,而非MSDN上说的BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG

5、因为是连续加密或者解密所这里需要使用初始化向量参数,用于保存前一次加密或解密时的内容

你可能感兴趣的:(CNG,安全,windows,c++)