Шифрование данных с помощью CNG
Основное использование любого API шифрования заключается в шифровании и расшифровке данных. CNG позволяет шифровать данные с помощью минимального количества вызовов функций и выполнять все операции управления памятью. Хотя многие сведения о реализации протокола остаются за пользователем, CNG предоставляет примитивы, которые выполняют фактические задачи шифрования и расшифровки данных.
Шифрование данных
Чтобы зашифровать данные, выполните следующие действия.
Откройте поставщик алгоритмов, поддерживающий шифрование, например BCRYPT_DES_ALGORITHM.
Создайте ключ для шифрования данных. Ключ можно создать с помощью любой из следующих функций:
- BCryptGenerateKeyPair или BCryptImportKeyPair для асимметричных поставщиков.
- BCryptGenerateSymmetricKey или BCryptImportKey для симметричных поставщиков.
Примечание
Шифрование и расшифровка данных с помощью асимметричного ключа требует больших вычислительных ресурсов по сравнению с шифрованием симметричного ключа. Если необходимо зашифровать данные с помощью асимметричного ключа, следует зашифровать данные с помощью симметричного ключа, зашифровать симметричный ключ асимметричным ключом и включить зашифрованный симметричный ключ в сообщение. Затем получатель может расшифровать симметричный ключ и использовать симметричный ключ для расшифровки данных.
Получение размера зашифрованных данных. Это зависит от алгоритма шифрования, схемы заполнения (если таковые есть) и размера данных, которые необходимо зашифровать. Размер зашифрованных данных можно получить с помощью функции BCryptEncrypt , передав значение NULL для параметра pbOutput . Все остальные параметры должны быть теми же, что и при фактическом шифровании данных, за исключением параметра pbInput , который в данном случае не используется.
Вы можете зашифровать данные на месте с помощью того же буфера или зашифровать данные в отдельный буфер.
Если вы хотите зашифровать данные на месте, передайте указатель буфера в виде открытого текста для параметров pbInput и pbOutput в функции BCryptEncrypt . Возможно, размер зашифрованных данных будет больше, чем размер незашифрованных данных, поэтому буфер открытого текста должен быть достаточно большим для хранения зашифрованных данных, а не только открытого текста. Для выделения буфера в виде открытого текста можно использовать размер, полученный на шаге 3.
Если вы хотите зашифровать данные в отдельный буфер, выделите буфер памяти для зашифрованных данных, используя размер, полученный на шаге 3.
Вызовите функцию BCryptEncrypt для шифрования данных. Эта функция записывает зашифрованные данные в расположение, указанное в параметре pbOutput .
При необходимости сохраняйте зашифрованные данные.
Повторяйте шаги 5 и 6, пока все данные не будут зашифрованы.
Пример шифрования данных
В следующем примере показано, как шифровать данные с помощью 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 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);
}
}
Расшифровка данных
Чтобы расшифровать данные, выполните следующие действия.
Откройте поставщик алгоритмов, поддерживающий шифрование, например BCRYPT_DES_ALGORITHM.
Получите ключ, с которым были зашифрованы данные, и используйте его для получения дескриптора ключа.
Получение размера расшифрованных данных. Он основан на алгоритме шифрования, схеме заполнения (если таковой имеется) и размере данных, которые необходимо расшифровать. Размер зашифрованных данных можно получить с помощью функции BCryptDecrypt , передав значение NULL для параметра pbOutput . Параметры, определяющие схему заполнения и вектор инициализации (IV), должны быть теми же, что и при шифровании данных, за исключением параметра pbInput , который в данном случае не используется.
Выделите буфер памяти для расшифрованных данных.
Вы можете расшифровать данные на месте с помощью того же буфера или расшифровать данные в отдельный буфер.
Если вы хотите расшифровать данные на месте, передайте указатель буфера зашифрованного текста для параметров pbInput и pbOutput в функции BCryptDecrypt .
Если вы хотите расшифровать данные в отдельный буфер, выделите буфер памяти для расшифрованных данных, используя размер, полученный на шаге 3.
Вызовите функцию BCryptDecrypt для расшифровки данных. Параметры, определяющие схему заполнения и iv, должны совпадать с параметрами, когда данные были зашифрованы. Эта функция записывает расшифрованные данные в расположение, указанное в параметре pbOutput .
При необходимости сохраните расшифрованные данные.
Повторяйте шаги 5 и 6, пока все данные не будут расшифрованы.