Contents
Introduction
This article explains some Cryptography API Next Generation (CNG) functions. This article is written under Windows Vista Business It's using Visual Studio C++ 2005 (Standard edition Sp1), Windows SDK and CNG SDK.
It's useful to:
- save documents in a secure environment, especially over non-secure media such as the Internet
- encrypt any kind of file, image, mp3 or document
- create the product key of a software (with some adjustment)
Note: CNG is currently supported only on Windows Vista and it cannot be used with VB or C#. In order to build Windows applications with Visual Studio, you will need the Windows Vista SDK, which provides the documentation, samples, header files, libraries, and tools you need to develop applications that run on Windows. The CNG SDK is now available for download at the Microsoft Download Center.
Background
The basic idea is to encrypt using a CNG a file with a simple GUI. The 3 steps necessary are:
- select Cryptographic Operations
- select the original file and the file to encrypt
- select the encryption key
The Application
I have created the project as an MFC application. The application has a single interface wherein you choose: the file you want to encrypt/decrypt, the operation methods (encrypt/decrypt) and the password. In the main dialog, you find a listbox
; it catalogs the errors that occur as well as other useful information.
Set Up Visual Studio 2005
To use this class in Visual C++ 2005, you must add to the project properties thus:
- At C++ General in the "Additional Include Directories," add this path: C:\Program Files\Microsoft CNG Development Kit\Include.
- At "Linker General" in the "Additional Library Directories," add this path: C:\Program Files\Microsoft CNG Development Kit\Lib\X86.
- At "Linker Input," "Additional Dependencies", add bcrypt.lib.
Using the Code
I created the class CMyCNGCryptFile
that uses CNG. It has 3 public
methods:
EnumProviders
- enumerate the registered providers CryptFile
- encrypt or decrypt an input file GetLastError
- return the last error that occurred in CryptFile
or EnumProviders
The Use of Cryptography
The steps involved in this application are:
- Open the algorithm provider
- Create or import a key
- Get and set algorithm properties
- Perform cryptographic operations
- Close the algorithm provider
The CNG API
To open the algorithm provider:
BCryptOpenAlgorithmProvider
To import a key:
BCryptGenerateSymmetricKey
To create a key:
BCryptCreateHash
BCryptHashData
BCryptFinishHash
BCryptGenerateSymmetricKey
To get the algorithm properties:
To set the algorithm properties:
To perform cryptographic operations:
BCryptEncrypt
BCryptDecrypt
To enumerate the providers:
BCryptEnumRegisteredProviders
To close the algorithm provider:
BCryptCloseAlgorithmProvider
To destroy the key:
To destroy the hash:
The Use of CMyCNGCryptFile
CryptFile
bool CryptFile(bool bEncrypt, CString sFileToOpen,CString sFileToCrypt,CString sKey)
This is the main method called from the Dlg
. It takes the Operation to perform, the InputFile
, the OutputFile
and the CryptKey
. The steps involved are:
- Open the algorithm provider with
OpenMSPrimitiveProviderAES
- Create a key with
CreateSymmetricKey_AES_CBC
, or import a key with CreateSymmetricKey_SHA1_Hash
- Retrieve the buffer from file in
- Perform cryptographic operations with
Crypt
for the intermediate byte and CryptLastByte
for the last byte - Save crypt data to file out
OpenMSPrimitiveProviderAES
This method opens a handle to the AES provider.
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
This method retrieves a key stored in the program as a static constant BYTE
variable called rgbAES128Key
. First, I get the property of the algorithm with BCryptGetProperty
. Then, I use the algorithm provider handle to obtain implementation details for the algorithm, such as the key size and IV size. Next, I allocate it in the heap and modify the properties of the algorithm with BCryptSetProperty
. Here, I want to use BCRYPT_CHAIN_MODE_CBC
block cipher chaining with AES. I set the BCRYPT_CHAINING_MODE
property of an AES algorithm to BCRYPT_CHAIN_MODE_CBC
. Now, I create an ephemeral key with the 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
This method retrieves a key from the user. First, I open a new algorithm SHA1
with BCryptOpenAlgorithmProvider
. I use SHA1
because the provider supports the hash interface used later. Then I get the property of the algorithm with BCryptGetProperty
. I use the algorithm provider handle to obtain implementation details for the algorithm, such as the key size and hash size. After this, I allocate it in the heap and create the hash object for the key with BCryptCreateHash
. Next, I use the BCryptHashData
function to perform a one-way hash on a data buffer. I then retrieve the hash value for the data accumulated from prior calls to BCryptHashData
. To do this, I use the BCryptFinishHash
. Now I modify the properties of the algorithm with BCryptSetProperty
. Here, I want to use BCRYPT_CHAIN_MODE_CBC
block cipher chaining with AES. I set the BCRYPT_CHAINING_MODE
property of an AES algorithm to BCRYPT_CHAIN_MODE_CBC
. Finally, I create an ephemeral key with the 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
This method performs cryptographic operations, such as encrypting or decrypting data, by using the BCryptEncrypt
or BCryptDecrypt
functions. I use this method to encrypt data with the same length of ciphertext. If the data must encrypt, I use BCryptEncrypt
, else I use 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
I use this method to encrypt data with different lengths of ciphertext
. I call BCryptEncrypt
or BCryptDecrypt
twice. The first time, I obtain the size of the encrypted data. The second time, I retrieve the 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
This method retrieves the current providers installed on the machine. I call the function BCryptEnumRegisteredProviders
to retrieve information about the registered providers. I enumerate the providers retrieved from pProviders
, a PCRYPT_PROVIDERS
structure.
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
The destructor closes the algorithm provider, deletes all of the pointers stored for memory leaks and destroys the key hash. I call the function BCryptCloseAlgorithmProvider
to close the algorithm provider. I call the function BCryptDestroyKey
to destroy the key and then I call the function BCryptDestroyHash
destroy the hash. Finally, I enumerate the providers retrieved from pProviders
, a PCRYPT_PROVIDERS
structure:
CMyCNGCryptFile::~CMyCNGCryptFile()
{
BCryptCloseAlgorithmProvider(m_hAesAlg,0);
BCryptDestroyKey(m_hKey);
HeapFree(GetProcessHeap(), 0, m_pbKeyObject);
HeapFree(GetProcessHeap(), 0, m_pbIV);
BCryptDestroyHash(m_hHash);
free(m_pbHashObject);
BCryptCloseAlgorithmProvider(m_hHashAlg,0);
}
Points of Interest
To deploy the application, I learned the MSDN Cryptography API: Next Generation. I think I'll be adding Signing Data, BLOB and the provider registration information soon. Stay tuned.
History
- 6th May, 2007 - First release