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






4.91/5 (36投票s)
Cryptography API: Next Generation (CNG) - 如何使用 C++ 编程加密文档(无需理解密码学或安全知识)。
目录
引言
本文介绍了 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 中使用此类,您必须在项目属性中进行以下设置:
- 在 C/C++ -> 常规 -> “其他包含目录”中,添加此路径:C:\Program Files\Microsoft CNG Development Kit\Include。
- 在“链接器”->“常规”->“其他库目录”中,添加此路径:C:\Program Files\Microsoft CNG Development Kit\Lib\X86。
- 在“链接器”->“输入”->“其他依赖项”中,添加 bcrypt.lib。
使用代码
我创建了使用 CNG 的 CMyCNGCryptFile
类。它有 3 个 public
方法:
EnumProviders
- 枚举已注册的提供程序CryptFile
- 加密或解密输入文件GetLastError
- 返回CryptFile
或EnumProviders
中发生的最后一个错误
加密的使用
此应用程序涉及的步骤包括:
- 打开算法提供程序
- 创建或导入密钥
- 获取和设置算法属性
- 执行加密操作
- 关闭算法提供程序
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 方法。它接受操作、InputFile
、OutputFile
和 CryptKey
。涉及的步骤是:
- 使用
OpenMSPrimitiveProviderAES
打开算法提供程序 - 使用
CreateSymmetricKey_AES_CBC
创建密钥,或使用CreateSymmetricKey_SHA1_Hash
导入密钥 - 从输入文件中检索缓冲区
- 使用
Crypt
进行中间字节的加密操作,使用CryptLastByte
进行最后一个字节的操作 - 将加密数据保存到输出文件
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
此方法执行加密操作,例如加密或解密数据,使用 BCryptEncrypt
或 BCryptDecrypt
函数。我使用此方法以相同长度的密文加密数据。如果数据需要加密,我使用 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
的数据。我调用 BCryptEncrypt
或 BCryptDecrypt
两次。第一次,我获取加密数据的大小。第二次,我检索 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 日 - 首次发布