Encrypting Data with CNG
The primary use of any cryptography API is to encrypt and decrypt data. CNG allows you to encrypt data by using a minimum number of function calls and allows you to perform all of the memory management. While many of the protocol implementation details are left up to the user, CNG provides the primitives that perform the actual data encryption and decryption tasks.
Encrypting Data
To encrypt data, perform the following steps:
Open an algorithm provider that supports encryption, such as BCRYPT_DES_ALGORITHM.
Create a key to encrypt the data with. A key can be created by using any of the following functions:
- BCryptGenerateKeyPair or BCryptImportKeyPair for asymmetric providers.
- BCryptGenerateSymmetricKey or BCryptImportKey for symmetric providers.
Note
Data encryption and decryption with an asymmetric key is computationally intensive compared to symmetric key encryption. If you need to encrypt data with an asymmetric key, you should encrypt the data with a symmetric key, encrypt the symmetric key with an asymmetric key, and include the encrypted symmetric key with the message. The recipient can then decrypt the symmetric key and use the symmetric key to decrypt the data.
Obtain the size of the encrypted data. This is based on the encryption algorithm, the padding scheme (if any), and the size of the data to be encrypted. You can obtain the encrypted data size by using the BCryptEncrypt function, passing NULL for the pbOutput parameter. All other parameters must be the same as when the data is actually encrypted except for the pbInput parameter, which is not used in this case.
You can either encrypt the data in place with the same buffer or encrypt the data into a separate buffer.
If you want to encrypt the data in place, pass the plaintext buffer pointer for both the pbInput and pbOutput parameters in the BCryptEncrypt function. It is possible that the encrypted data size will be larger than the unencrypted data size, so the plaintext buffer must be large enough to hold the encrypted data, not just the plaintext. You can use the size obtained in step 3 to allocate the plaintext buffer.
If you want to encrypt the data into a separate buffer, allocate a memory buffer for the encrypted data by using the size obtained in step 3.
Call the BCryptEncrypt function to encrypt the data. This function will write the encrypted data to the location provided in the pbOutput parameter.
Persist the encrypted data as needed.
Repeat steps 5 and 6 until all of the data has been encrypted.
Encrypting Data Example
The following example shows how to encrypt data with CNG by using the advanced encryption standard symmetric encryption algorithm.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) Microsoft. All rights reserved.
/*++
Abstract:
Sample program for AES-CBC encryption using CNG
--*/
#include <windows.h>
#include <stdio.h>
#include <bcrypt.h>
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#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
};
void PrintBytes(
IN BYTE *pbPrintData,
IN DWORD cbDataLen)
{
DWORD dwCount = 0;
for(dwCount=0; dwCount < cbDataLen;dwCount++)
{
printf("0x%02x, ",pbPrintData[dwCount]);
if(0 == (dwCount + 1 )%10) putchar('\n');
}
}
void __cdecl wmain(
int argc,
__in_ecount(argc) LPWSTR *wargv)
{
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;
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(wargv);
// Open an algorithm handle.
if(!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(
&hAesAlg,
BCRYPT_AES_ALGORITHM,
NULL,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptOpenAlgorithmProvider\n", status);
goto Cleanup;
}
// Calculate the size of the buffer to hold the KeyObject.
if(!NT_SUCCESS(status = BCryptGetProperty(
hAesAlg,
BCRYPT_OBJECT_LENGTH,
(PBYTE)&cbKeyObject,
sizeof(DWORD),
&cbData,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
goto Cleanup;
}
// Allocate the key object on the heap.
pbKeyObject = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbKeyObject);
if(NULL == pbKeyObject)
{
wprintf(L"**** memory allocation failed\n");
goto Cleanup;
}
// Calculate the block length for the IV.
if(!NT_SUCCESS(status = BCryptGetProperty(
hAesAlg,
BCRYPT_BLOCK_LENGTH,
(PBYTE)&cbBlockLen,
sizeof(DWORD),
&cbData,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
goto Cleanup;
}
// Determine whether the cbBlockLen is not longer than the IV length.
if (cbBlockLen > sizeof (rgbIV))
{
wprintf (L"**** block length is longer than the provided IV length\n");
goto Cleanup;
}
// Allocate a buffer for the IV. The buffer is consumed during the
// encrypt/decrypt process.
pbIV= (PBYTE) HeapAlloc (GetProcessHeap (), 0, cbBlockLen);
if(NULL == pbIV)
{
wprintf(L"**** memory allocation failed\n");
goto Cleanup;
}
memcpy(pbIV, rgbIV, cbBlockLen);
if(!NT_SUCCESS(status = BCryptSetProperty(
hAesAlg,
BCRYPT_CHAINING_MODE,
(PBYTE)BCRYPT_CHAIN_MODE_CBC,
sizeof(BCRYPT_CHAIN_MODE_CBC),
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptSetProperty\n", status);
goto Cleanup;
}
// Generate the key from supplied input key bytes.
if(!NT_SUCCESS(status = BCryptGenerateSymmetricKey(
hAesAlg,
&hKey,
pbKeyObject,
cbKeyObject,
(PBYTE)rgbAES128Key,
sizeof(rgbAES128Key),
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptGenerateSymmetricKey\n", status);
goto Cleanup;
}
// Save another copy of the key for later.
if(!NT_SUCCESS(status = BCryptExportKey(
hKey,
NULL,
BCRYPT_OPAQUE_KEY_BLOB,
NULL,
0,
&cbBlob,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptExportKey\n", status);
goto Cleanup;
}
// Allocate the buffer to hold the BLOB.
pbBlob = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbBlob);
if(NULL == pbBlob)
{
wprintf(L"**** memory allocation failed\n");
goto Cleanup;
}
if(!NT_SUCCESS(status = BCryptExportKey(
hKey,
NULL,
BCRYPT_OPAQUE_KEY_BLOB,
pbBlob,
cbBlob,
&cbBlob,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptExportKey\n", status);
goto Cleanup;
}
cbPlainText = sizeof(rgbPlaintext);
pbPlainText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbPlainText);
if(NULL == pbPlainText)
{
wprintf(L"**** memory allocation failed\n");
goto Cleanup;
}
memcpy(pbPlainText, rgbPlaintext, sizeof(rgbPlaintext));
//
// Get the output buffer size.
//
if(!NT_SUCCESS(status = BCryptEncrypt(
hKey,
pbPlainText,
cbPlainText,
NULL,
pbIV,
cbBlockLen,
NULL,
0,
&cbCipherText,
BCRYPT_BLOCK_PADDING)))
{
wprintf(L"**** Error 0x%x returned by BCryptEncrypt\n", status);
goto Cleanup;
}
pbCipherText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbCipherText);
if(NULL == pbCipherText)
{
wprintf(L"**** memory allocation failed\n");
goto Cleanup;
}
// Use the key to encrypt the plaintext buffer.
// For block sized messages, block padding will add an extra block.
if(!NT_SUCCESS(status = BCryptEncrypt(
hKey,
pbPlainText,
cbPlainText,
NULL,
pbIV,
cbBlockLen,
pbCipherText,
cbCipherText,
&cbData,
BCRYPT_BLOCK_PADDING)))
{
wprintf(L"**** Error 0x%x returned by BCryptEncrypt\n", status);
goto Cleanup;
}
// Destroy the key and reimport from saved BLOB.
if(!NT_SUCCESS(status = BCryptDestroyKey(hKey)))
{
wprintf(L"**** Error 0x%x returned by BCryptDestroyKey\n", status);
goto Cleanup;
}
hKey = 0;
if(pbPlainText)
{
HeapFree(GetProcessHeap(), 0, pbPlainText);
}
pbPlainText = NULL;
// We can reuse the key object.
memset(pbKeyObject, 0 , cbKeyObject);
// Reinitialize the IV because encryption would have modified it.
memcpy(pbIV, rgbIV, cbBlockLen);
if(!NT_SUCCESS(status = BCryptImportKey(
hAesAlg,
NULL,
BCRYPT_OPAQUE_KEY_BLOB,
&hKey,
pbKeyObject,
cbKeyObject,
pbBlob,
cbBlob,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptGenerateSymmetricKey\n", status);
goto Cleanup;
}
//
// Get the output buffer size.
//
if(!NT_SUCCESS(status = BCryptDecrypt(
hKey,
pbCipherText,
cbCipherText,
NULL,
pbIV,
cbBlockLen,
NULL,
0,
&cbPlainText,
BCRYPT_BLOCK_PADDING)))
{
wprintf(L"**** Error 0x%x returned by BCryptDecrypt\n", status);
goto Cleanup;
}
pbPlainText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbPlainText);
if(NULL == pbPlainText)
{
wprintf(L"**** memory allocation failed\n");
goto Cleanup;
}
if(!NT_SUCCESS(status = BCryptDecrypt(
hKey,
pbCipherText,
cbCipherText,
NULL,
pbIV,
cbBlockLen,
pbPlainText,
cbPlainText,
&cbPlainText,
BCRYPT_BLOCK_PADDING)))
{
wprintf(L"**** Error 0x%x returned by BCryptDecrypt\n", status);
goto Cleanup;
}
if (0 != memcmp(pbPlainText, (PBYTE)rgbPlaintext, sizeof(rgbPlaintext)))
{
wprintf(L"Expected decrypted text comparison failed.\n");
goto Cleanup;
}
wprintf(L"Success!\n");
Cleanup:
if(hAesAlg)
{
BCryptCloseAlgorithmProvider(hAesAlg,0);
}
if (hKey)
{
BCryptDestroyKey(hKey);
}
if(pbCipherText)
{
HeapFree(GetProcessHeap(), 0, pbCipherText);
}
if(pbPlainText)
{
HeapFree(GetProcessHeap(), 0, pbPlainText);
}
if(pbKeyObject)
{
HeapFree(GetProcessHeap(), 0, pbKeyObject);
}
if(pbIV)
{
HeapFree(GetProcessHeap(), 0, pbIV);
}
}
Decrypting Data
To decrypt data, perform the following steps:
Open an algorithm provider that supports encryption, such as BCRYPT_DES_ALGORITHM.
Obtain the key that the data was encrypted with, and use that key to obtain a handle for the key.
Obtain the size of the decrypted data. This is based on the encryption algorithm, the padding scheme (if any), and the size of the data to be decrypted. You can obtain the encrypted data size by using the BCryptDecrypt function, passing NULL for the pbOutput parameter. The parameters that specify the padding scheme and initialization vector (IV) must be the same as when the data was encrypted except for the pbInput parameter, which is not used in this case.
Allocate a memory buffer for the decrypted data.
You can either decrypt the data in place by using the same buffer, or decrypt the data into a separate buffer.
If you want to decrypt the data in place, pass the ciphertext buffer pointer for both the pbInput and pbOutput parameters in the BCryptDecrypt function.
If you want to decrypt the data into a separate buffer, allocate a memory buffer for the decrypted data by using the size obtained in step 3.
Call the BCryptDecrypt function to decrypt the data. The parameters that specify the padding scheme and IV must be the same as when the data was encrypted. This function will write the decrypted data to the location provided in the pbOutput parameter.
Persist the decrypted data as needed.
Repeat steps 5 and 6 until all of the data has been decrypted.