Creating a Hash with CNG
A hash is a one way operation that is performed on a block of data to create a unique hash value that represents the contents of the data. No matter when the hash is performed, the same hashing algorithm performed on the same data will always produce the same hash value. If any of the data changes, the hash value will change appropriately.
Hashes are not useful for encrypting data because they are not intended to be used to reproduce the original data from the hash value. Hashes are most useful to verify the integrity of the data when used with an asymmetric signing algorithm. For example, if you hashed a text message, signed the hash, and included the signed hash value with the original message, the recipient could verify the signed hash, create the hash value for the received message, and then compare this hash value with the signed hash value included with the original message. If the two hash values are identical, the recipient can be reasonably sure that the original message has not been modified.
The size of the hash value is fixed for a particular hashing algorithm. What this means is that no matter how large or small the data block is, the hash value will always be the same size. As an example, the SHA256 hashing algorithm has a hash value size of 256 bits.
Creating a Hashing Object
To create a hash using CNG, perform the following steps:
Open an algorithm provider that supports the desired algorithm. Typical hashing algorithms include MD2, MD4, MD5, SHA-1, and SHA256. Call the BCryptOpenAlgorithmProvider function and specify the appropriate algorithm identifier in the pszAlgId parameter. The function returns a handle to the provider.
Perform the following steps to create the hashing object:
- Obtain the size of the object by calling the BCryptGetProperty function to retrieve the BCRYPT_OBJECT_LENGTH property.
- Allocate memory to hold the hash object.
- Create the object by calling the BCryptCreateHash function.
Hash the data. This involves calling the BCryptHashData function one or more times. Each call appends the specified data to the hash.
Perform the following steps to obtain the hash value:
- Retrieve the size of the value by calling the BCryptGetProperty function to get the BCRYPT_HASH_LENGTH property.
- Allocate memory to hold the value.
- Retrieve the hash value by calling the BCryptFinishHash function. After this function has been called, the hash object is no longer valid.
To complete this procedure, you must perform the following cleanup steps:
Close the hash object by passing the hash handle to the BCryptDestroyHash function.
Free the memory you allocated for the hash object.
If you will not be creating any more hash objects, close the algorithm provider by passing the provider handle to the BCryptCloseAlgorithmProvider function.
If you will be creating more hash objects, we suggest you reuse the algorithm provider rather than creating and destroying the same type of algorithm provider many times.
When you have finished using the hash value memory, free it.
The following example shows how to create a hash value by using CNG.
// 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 SHA 256 hashing using CNG
--*/
#include <windows.h>
#include <stdio.h>
#include <bcrypt.h>
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
static const BYTE rgbMsg[] =
{
0x61, 0x62, 0x63
};
void __cdecl wmain(
int argc,
__in_ecount(argc) LPWSTR *wargv)
{
BCRYPT_ALG_HANDLE hAlg = NULL;
BCRYPT_HASH_HANDLE hHash = NULL;
NTSTATUS status = STATUS_UNSUCCESSFUL;
DWORD cbData = 0,
cbHash = 0,
cbHashObject = 0;
PBYTE pbHashObject = NULL;
PBYTE pbHash = NULL;
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(wargv);
//open an algorithm handle
if(!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(
&hAlg,
BCRYPT_SHA256_ALGORITHM,
NULL,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptOpenAlgorithmProvider\n", status);
goto Cleanup;
}
//calculate the size of the buffer to hold the hash object
if(!NT_SUCCESS(status = BCryptGetProperty(
hAlg,
BCRYPT_OBJECT_LENGTH,
(PBYTE)&cbHashObject,
sizeof(DWORD),
&cbData,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
goto Cleanup;
}
//allocate the hash object on the heap
pbHashObject = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbHashObject);
if(NULL == pbHashObject)
{
wprintf(L"**** memory allocation failed\n");
goto Cleanup;
}
//calculate the length of the hash
if(!NT_SUCCESS(status = BCryptGetProperty(
hAlg,
BCRYPT_HASH_LENGTH,
(PBYTE)&cbHash,
sizeof(DWORD),
&cbData,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
goto Cleanup;
}
//allocate the hash buffer on the heap
pbHash = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbHash);
if(NULL == pbHash)
{
wprintf(L"**** memory allocation failed\n");
goto Cleanup;
}
//create a hash
if(!NT_SUCCESS(status = BCryptCreateHash(
hAlg,
&hHash,
pbHashObject,
cbHashObject,
NULL,
0,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptCreateHash\n", status);
goto Cleanup;
}
//hash some data
if(!NT_SUCCESS(status = BCryptHashData(
hHash,
(PBYTE)rgbMsg,
sizeof(rgbMsg),
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptHashData\n", status);
goto Cleanup;
}
//close the hash
if(!NT_SUCCESS(status = BCryptFinishHash(
hHash,
pbHash,
cbHash,
0)))
{
wprintf(L"**** Error 0x%x returned by BCryptFinishHash\n", status);
goto Cleanup;
}
wprintf(L"Success!\n");
Cleanup:
if(hAlg)
{
BCryptCloseAlgorithmProvider(hAlg,0);
}
if (hHash)
{
BCryptDestroyHash(hHash);
}
if(pbHashObject)
{
HeapFree(GetProcessHeap(), 0, pbHashObject);
}
if(pbHash)
{
HeapFree(GetProcessHeap(), 0, pbHash);
}
}
Creating a Reusable Hashing Object
Beginning with Windows 8 and Windows Server 2012, you can create a reusable hashing object for scenarios that require you to compute multiple hashes or HMACs in rapid succession. Do this by specifying the BCRYPT_HASH_REUSABLE_FLAG when calling the BCryptOpenAlgorithmProvider function. All Microsoft hash algorithm providers support this flag. A hashing object created by using this flag can be reused immediately after calling BCryptFinishHash just as if it had been freshly created by calling BCryptCreateHash. Perform the following steps to create a reusable hashing object:
Open an algorithm provider that supports the desired hashing algorithm. Call the BCryptOpenAlgorithmProvider function and specify the appropriate algorithm identifier in the pszAlgId parameter and BCRYPT_HASH_REUSABLE_FLAG in the dwFlags parameter. The function returns a handle to the provider.
Perform the following steps to create the hashing object:
- Obtain the size of the object by calling the BCryptGetProperty function to retrieve the BCRYPT_OBJECT_LENGTH property.
- Allocate memory to hold the hash object.
- Create the object by calling the BCryptCreateHash function. Specify BCRYPT_HASH_REUSABLE_FLAG in the dwFlags parameter.
Hash the data by calling the BCryptHashData function.
Perform the following steps to obtain the hash value:
- Obtain the size of the hash value by calling the BCryptGetProperty function to get the BCRYPT_HASH_LENGTH property.
- Allocate memory to hold the value.
- Get the hash value by calling BCryptFinishHash.
To reuse the hashing object with new data, go to step 3.
To complete this procedure, you must perform the following cleanup steps:
- Close the hash object by passing the hash handle to the BCryptDestroyHash function.
- Free the memory you allocated for the hash object.
- If you will not be creating any more hash objects, close the algorithm provider by passing the provider handle to the BCryptCloseAlgorithmProvider function.
- When you have finished using the hash value memory, free it.
Duplicating a Hash Object
In some circumstances, it may be useful to hash some amount of common data and then create two separate hash objects from the common data. You do not have to create two separate hash objects and hash the common data twice to accomplish this. You can create a single hash object and add all of the common data to the hash object. Then, you can use the BCryptDuplicateHash function to create a duplicate of the original hash object. The duplicate hash object contains all of the same state information and hashed data as the original, but it is a completely independent hash object. You can now add the unique data to each of the hash objects and obtain the hash value as shown in the example. This technique is useful when hashing a possibly large amount of common data. You only have to add the common data to the original hash one time, and then you can duplicate the hash object to obtain a unique hash object.