65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 CNG 加密文件的简单方法

2007 年 5 月 8 日

CPOL

5分钟阅读

viewsIcon

142992

downloadIcon

2916

Cryptography API: Next Generation (CNG) - 如何使用 C++ 编程加密文档(无需理解密码学或安全知识)。

Screenshot - CNGCryptFileSample1.gif

目录

引言

本文介绍了 Cryptography API Next Generation (CNG) 的一些函数。本文是在 Windows Vista Business 下编写的,使用了 Visual Studio C++ 2005 (Standard edition Sp1)、Windows SDK 和 CNG SDK。

它有助于

  • 在安全的环境中保存文档,尤其是在互联网等不安全介质上
  • 加密任何类型的文件、图片、mp3 或文档
  • 创建软件的产品密钥(稍作调整)

注意:CNG 目前仅在 Windows Vista 上受支持,不能与 VB 或 C# 一起使用。为了使用 Visual Studio 构建 Windows 应用程序,您需要 Windows Vista SDK,它提供了在 Windows 上运行的应用程序所需的文档、示例、头文件、库和工具。CNG SDK 现在可在 Microsoft 下载中心 下载

背景

基本思想是使用一个简单的 GUI 加密 CNG 文件。所需的 3 个步骤是

  • 选择加密操作
  • 选择原始文件和要加密的文件
  • 选择加密密钥

应用程序

我创建了一个 MFC 应用程序项目。该应用程序有一个单一的界面,您可以在其中选择:要加密/解密的文件、操作方法(加密/解密)和密码。在主对话框中,您会找到一个 listbox;它会记录发生的错误以及其他有用的信息。

设置 Visual Studio 2005

要在 Visual C++ 2005 中使用此类,您必须在项目属性中进行以下设置:

  1. 在 C/C++ -> 常规 -> “其他包含目录”中,添加此路径:C:\Program Files\Microsoft CNG Development Kit\Include

    C++ General

  2. 在“链接器”->“常规”->“其他库目录”中,添加此路径:C:\Program Files\Microsoft CNG Development Kit\Lib\X86

    Linker General

  3. 在“链接器”->“输入”->“其他依赖项”中,添加 bcrypt.lib

    Linker Input

使用代码

我创建了使用 CNG 的 CMyCNGCryptFile 类。它有 3 个 public 方法:

  • EnumProviders - 枚举已注册的提供程序
  • CryptFile - 加密或解密输入文件
  • GetLastError - 返回 CryptFileEnumProviders 中发生的最后一个错误

加密的使用

此应用程序涉及的步骤包括:

  1. 打开算法提供程序
  2. 创建或导入密钥
  3. 获取和设置算法属性
  4. 执行加密操作
  5. 关闭算法提供程序

CNG API

打开算法提供程序

  • BCryptOpenAlgorithmProvider

导入密钥

  • BCryptGenerateSymmetricKey

创建密钥

  • BCryptCreateHash
  • BCryptHashData
  • BCryptFinishHash
  • BCryptGenerateSymmetricKey

获取算法属性

  • BCryptGetProperty

设置算法属性

  • BCryptSetProperty

执行加密操作

  • BCryptEncrypt
  • BCryptDecrypt

枚举提供程序

  • BCryptEnumRegisteredProviders

关闭算法提供程序

  • BCryptCloseAlgorithmProvider

销毁密钥

  • BCryptDestroyKey

销毁哈希

  • BCryptDestroyHash

CMyCNGCryptFile 的使用

CryptFile

bool CryptFile(bool bEncrypt, CString sFileToOpen,CString sFileToCrypt,CString sKey)

这是从 Dlg 调用 main 方法。它接受操作、InputFileOutputFileCryptKey。涉及的步骤是:

  1. 使用 OpenMSPrimitiveProviderAES 打开算法提供程序
  2. 使用 CreateSymmetricKey_AES_CBC 创建密钥,或使用 CreateSymmetricKey_SHA1_Hash 导入密钥
  3. 从输入文件中检索缓冲区
  4. 使用 Crypt 进行中间字节的加密操作,使用 CryptLastByte 进行最后一个字节的操作
  5. 将加密数据保存到输出文件

OpenMSPrimitiveProviderAES

此方法打开 AES 提供程序的句柄。

bool CMyCNGCryptFile::OpenMSPrimitiveProviderAES()
{
    NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
    ntStatus  = BCryptOpenAlgorithmProvider( &m_hAesAlg, 
        BCRYPT_AES_ALGORITHM, NULL, 0);
    switch (ntStatus)
    {
    case STATUS_SUCCESS:
        return true;
    case STATUS_INVALID_PARAMETER:
    case STATUS_NO_MEMORY:
    default:
    //... Check and log the error ...        
    }
    return false;
}

CreateSymmetricKey_AES_CBC

此方法检索存储在程序中的密钥,该密钥是一个名为 rgbAES128Key 的静态常量 BYTE 变量。首先,我使用 BCryptGetProperty 获取算法的属性。然后,我使用算法提供程序句柄获取算法的实现细节,例如密钥大小和 IV 大小。接下来,我在堆上分配内存并使用 BCryptSetProperty 修改算法的属性。在此,我想使用 BCRYPT_CHAIN_MODE_CBC 块密码链与 AES。我将 AES 算法的 BCRYPT_CHAINING_MODE 属性设置为 BCRYPT_CHAIN_MODE_CBC。现在,我使用 BCryptGenerateSymmetricKey 创建一个临时密钥。

bool CMyCNGCryptFile::CreateSymmetricKey_AES_CBC(DWORD &cbKeyObject, 
    DWORD &cbIV )
{
    NTSTATUS    ntStatus = STATUS_UNSUCCESSFUL;
    DWORD        cbData    = 0; 

    cbKeyObject    = 0;
    cbIV  = 0;

    ntStatus = BCryptGetProperty(m_hAesAlg, BCRYPT_OBJECT_LENGTH, 
        (PBYTE)&cbKeyObject,  sizeof(DWORD), &cbData, 0);
    ...
    m_pbKeyObject = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbKeyObject);
    ...
    ntStatus = BCryptGetProperty( m_hAesAlg, BCRYPT_BLOCK_LENGTH, 
        (PBYTE)&cbIV, sizeof(DWORD), &cbData, 0);
    ...
    m_pbIV= (PBYTE) HeapAlloc (GetProcessHeap (), 0, cbIV);

    memcpy(m_pbIV, rgbIV, cbIV);

    ntStatus = BCryptSetProperty(m_hAesAlg, BCRYPT_CHAINING_MODE, 
        (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
    ... 
    ntStatus = BCryptGenerateSymmetricKey(m_hAesAlg, &m_hKey, m_pbKeyObject, 
        cbKeyObject, (PBYTE)rgbAES128Key, sizeof(rgbAES128Key), 0);
    ... 
    return true;
}

CreateSymmetricKey_SHA1_Hash

此方法从用户处检索密钥。首先,我使用 BCryptOpenAlgorithmProvider 打开一个新的 SHA1 算法。我使用 SHA1,因为提供程序支持稍后使用的哈希接口。然后我使用 BCryptGetProperty 获取算法的属性。我使用算法提供程序句柄获取算法的实现细节,例如密钥大小和哈希大小。在此之后,我在堆上分配内存,并使用 BCryptCreateHash 为密钥创建哈希对象。接下来,我使用 BCryptHashData 函数对数据缓冲区执行一次性哈希。然后,我检索先前调用 BCryptHashData 累积的数据的哈希值。为此,我使用 BCryptFinishHash。现在我使用 BCryptSetProperty 修改算法的属性。在此,我想使用 BCRYPT_CHAIN_MODE_CBC 块密码链与 AES。我将 AES 算法的 BCRYPT_CHAINING_MODE 属性设置为 BCRYPT_CHAIN_MODE_CBC。最后,我使用 BCryptGenerateSymmetricKey 创建一个临时密钥。

bool CMyCNGCryptFile::CreateSymmetricKey_SHA1_Hash(PCWSTR pwszText, 
    DWORD cbKeyObject)
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BCRYPT_KEY_HANDLE    hKey = NULL;
    
    DWORD               cbHashObject, cbResult;
    BYTE                rgbHash[20];
    DWORD                cbData    = 0; 

    ntStatus = BCryptOpenAlgorithmProvider(&m_hHashAlg,
                     BCRYPT_SHA1_ALGORITHM,NULL,0);
    ... 
    ntStatus = BCryptGetProperty(m_hAesAlg, BCRYPT_OBJECT_LENGTH,
                    (PBYTE)&cbKeyObject,  sizeof(DWORD), &cbData, 0);
    ...    
    ntStatus = BCryptGetProperty( m_hHashAlg,BCRYPT_OBJECT_LENGTH,
                    (PBYTE) &cbHashObject,sizeof(DWORD),&cbResult,0);
    ...
    ntStatus = BCryptCreateHash(m_hHashAlg, &m_hHash, m_pbHashObject,
                    cbHashObject, NULL, 0, 0 );

    ntStatus = BCryptHashData( m_hHash,  (PBYTE)pwszText, (ULONG)wcslen(
        pwszText), 0);

    ntStatus = BCryptFinishHash( m_hHash, rgbHash, sizeof(rgbHash), 0);
    ...
    ntStatus = BCryptGenerateSymmetricKey( m_hAesAlg, &hKey,  m_pbKeyObject, 
                cbKeyObject, rgbHash, SYMM_KEY_SIZE_SECRET,  0  );
    ...
    return true;
}

Crypt

此方法执行加密操作,例如加密或解密数据,使用 BCryptEncryptBCryptDecrypt 函数。我使用此方法以相同长度的密文加密数据。如果数据需要加密,我使用 BCryptEncrypt,否则我使用 BCryptEncrypt

bool CMyCNGCryptFile::Crypt(bool bEncrypt,PUCHAR pbufFileToOpen, 
    ULONG iBytesRead, ULONG cbIV, PBYTE pbufFileToSave, DWORD& iBufToSave)
{    
    NTSTATUS ntStatus =STATUS_UNSUCCESSFUL;    
    DWORD        cbCipherText        = 0;

    if ( bEncrypt )                     
        ntStatus = BCryptEncrypt(m_hKey, pbufFileToOpen, iBytesRead, NULL,
                   m_pbIV, cbIV, pbufFileToSave, iBytesRead, &iBufToSave, 0);

    else        
        ntStatus = BCryptDecrypt(m_hKey, pbufFileToOpen, iBytesRead, NULL,
                   m_pbIV, cbIV, pbufFileToSave, iBytesRead, &iBufToSave,  0);

    ...  
    return false;
}

CryptLastByte

我使用此方法加密具有不同长度 ciphertext 的数据。我调用 BCryptEncryptBCryptDecrypt 两次。第一次,我获取加密数据的大小。第二次,我检索 ciphertext

bool CMyCNGCryptFile::CryptLastByte(bool bEncrypt,PUCHAR pbufFileToOpen, 
    ULONG iBytesRead, ULONG cbIV, PBYTE pbufFileToSave, DWORD& iBufToSave)
{
    NTSTATUS ntStatus= STATUS_UNSUCCESSFUL;
    DWORD        cbCipherText        = 0;

    if (bEncrypt)
    {
        ntStatus = BCryptEncrypt(m_hKey, pbufFileToOpen, iBytesRead, NULL, 
                     m_pbIV, cbIV, NULL, 0, &cbCipherText, 
                     BCRYPT_BLOCK_PADDING);
        ...
        ntStatus = BCryptEncrypt( m_hKey, pbufFileToOpen, iBytesRead, NULL, 
                     m_pbIV, cbIV, pbufFileToSave, cbCipherText, 
                     &cbCipherText,BCRYPT_BLOCK_PADDING);
        iBufToSave        =    cbCipherText;
    ...    
    }
    else
    {
        ntStatus = BCryptDecrypt( m_hKey, pbufFileToOpen, iBytesRead, NULL, 
        m_pbIV, cbIV, NULL, 0, &cbCipherText, BCRYPT_BLOCK_PADDING);
    ...        
        ntStatus = BCryptDecrypt( m_hKey, pbufFileToOpen, iBytesRead, NULL, 
        m_pbIV, cbIV, pbufFileToSave, cbCipherText, &cbCipherText, 
        BCRYPT_BLOCK_PADDING);
        ...
    }
    return false;
}

EnumProviders

此方法检索计算机上当前安装的提供程序。我调用 BCryptEnumRegisteredProviders 函数来检索有关已注册提供程序的信息。我枚举从 pProviders(一个 PCRYPT_PROVIDERS 结构)检索到的提供程序。

bool CMyCNGCryptFile::EnumProviders(CStringList *lstRegisteredProviders)
{
    ...
    ntStatus = BCryptEnumRegisteredProviders(&cbBuffer, &pProviders);
    ...    
    for ( DWORD i = 0; i < pProviders->cProviders; i++)
    {
        sProvider.Format(_T("%s\n"), 
        pProviders->rgpszProviders[i]);
        lstRegisteredProviders->AddHead(sProvider);
    } 
    
    if (pProviders != NULL)
    {
        BCryptFreeBuffer(pProviders);
    }
    return true;
}

~CMyCNGCryptFile

析构函数关闭算法提供程序,删除所有为防止内存泄漏而存储的指针,并销毁密钥哈希。我调用 BCryptCloseAlgorithmProvider 函数来关闭算法提供程序。我调用 BCryptDestroyKey 函数来销毁密钥,然后调用 BCryptDestroyHash 函数销毁哈希。最后,我枚举从 pProviders(一个 PCRYPT_PROVIDERS 结构)检索到的提供程序。

CMyCNGCryptFile::~CMyCNGCryptFile()
{
    BCryptCloseAlgorithmProvider(m_hAesAlg,0);
    BCryptDestroyKey(m_hKey);
    HeapFree(GetProcessHeap(), 0, m_pbKeyObject);
    HeapFree(GetProcessHeap(), 0, m_pbIV);
    //Hash
    BCryptDestroyHash(m_hHash);
    free(m_pbHashObject);
    BCryptCloseAlgorithmProvider(m_hHashAlg,0);
}

值得关注的点

为了部署该应用程序,我学习了 MSDN Cryptography API: Next Generation。我认为我很快会添加数据签名、BLOB 和提供程序注册信息。敬请关注。

历史

  • 2007 年 5 月 6 日 - 首次发布
© . All rights reserved.