用CNG加密文件的简单方法
简介
文中用到了一些Cryptography API Next Generation(CNG)函数,开发环境为Windows Vista下的Visual C++ 2005 SP1标准版,加上Windows SDK及CNG SDK。
程序可适用于以下情况:
在安全环境下保存文档,但需要在不安全的媒质(如互联网)中传送。
加密文件,如图像、MP3、各类文档。
创建软件的产品密钥。
需要注意的是,CNG目前只支持Windows Vista,且不能使用在Visual Basic及C#中。要在Visual Studio中生成相应的Windows程序,可能还需要Windows Vista SDK及CNG SDK,两者都可以从微软官方网站下载获得。
背景
我们最初是想在一个简单的GUI程序中使用CNG来加密文件,需要以下三步:
1、 选择加密操作。
2、 选择需要加密的文件。
3、 选择加密密钥。
相关程序
此处创建了一个MFC应用程序,程序使用单文档界面,在其中可选择待加解密的文件、加密还是解密、密码;此外,还有一个列表框,用于显示其他信息。
另外,要在Visual C++ 2005中使用CNG SDK,还需要进行如下的项目设置:
1、 在“C/C++——General”项右方的“Additional Include Directories”中,添加以下目录:C:/Program Files/Microsoft CNG Development Kit/Include
2、 在“Link——General”项右方的“Additional Library Directories”中,添加以下目录:C:/Program Files/Microsoft CNG Development Kit/Lib/X86
3、 在“Linker——Input”项右方的“Additional Dependencies”中,添加“bcrypt.lib”。
相关代码
在此使用CNG创建了类CMyCNGCryptFile,它有三个公有方法:
EnumProviders:枚举出注册的提供者。
CryptFile:加密或解密一个文件。
GetLastError:返回发生在CryptFile或EnumProviders中的最后一个错误。
相关步骤如下:1、打开算法提供者;2、创建或导入一个密钥;3、获取或设置算法属性;4、执行操作;5、关闭算法提供者。
以下是CNG API:
打开算法提供者:
BCryptOpenAlgorithmProvider
导入密钥:
BCryptGenerateSymmetricKey
创建密钥:
BCryptCreateHash
BCryptHashData
BCryptFinishHash
BCryptGenerateSymmetricKey
获取或设置算法属性:
BCryptGetProperty
BCryptSetProperty
执行加解密操作:
BCryptEncrypt
BCryptDecrypt
枚举提供者:
BCryptEnumRegisteredProviders
关闭算法提供者:
BCryptCloseAlgorithmProvider
销毁密钥:
BCryptDestroyKey
销毁哈希:
BCryptDestroyHash
bool CryptFile(bool bEncrypt, CString sFileToOpen,CString sFileToCrypt,CString sKey)
这是从对话框中调用的主要方法,它接受要执行的操作、输入的文件、输出的文件、密钥作为参数,步骤如下:
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:
//... ...
}
return false;
}
CreateSymmetricKey_AES_CBC方法获取一个密钥,并把它作为一个静态常数BYTE变量rgbAES128Key存储在程序中。第一步,通过BCryptGetProperty取得算法属性,接着用算法提供者句柄得到算法的实现细节,如密钥大小及IV大小;第二步,把它分配在堆中,并通过BCryptSetProperty修改算法的属性。此处假定要使用BCRYPT_CHAIN_MODE_CBC,我们将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修改算法属性,像上面一样,把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函数执行加解密操作,另外,使用了相同长度的密文来加密数据。
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方法使用了不同长度的密文来加密数据,此处调用了BCryptEncrypt或BCryptDecrypt两次,第一次是为了得到加密数据的大小,第二次是得到密文。
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用于销毁哈希。最后,从PCRYPT_PROVIDERS结构的pProviders中枚举提供者。
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);
}