WinAPI - CryptDecrypt() не работает должным образом в AES 256
Я имел обыкновение работать с crypto++
в Visual Studio раньше, но теперь я хочу использовать wincrypt.h
API-функции для шифрования строки с AES 256 с помощью IV (режим cbc).
Я сделал следующие шаги, но я запутался CryptEncrypt()
а также CryptDecrypt()
функции, потому что кажется, что они не работают должным образом:
CryptAcquireContextA
определены для созданияCSP
:// create a cryptographic service provider (CSP) CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)
Для набора ключей я использую этот способ (ключ импорта):
CryptImportKey(hProv, (BYTE*)&AESBlob, sizeof(AES256KEYBLOB), NULL, CRYPT_EXPORTABLE, &hKey)
Размеры IV, Key, Plaintext:
#define DEFAULT_AES_KEY_SIZE 32 #define DEFAULT_IV_SIZE 16 #define BUFFER_FOR_PLAINTEXT 32
Это мое целое code
:
// handles for csp and key
HCRYPTPROV hProv = NULL;
HCRYPTPROV hKey = NULL;
BYTE szKey[DEFAULT_AES_KEY_SIZE + 1] = {0};
BYTE szIV[DEFAULT_IV_SIZE + 1] = {0};
// plain bytes
BYTE szPlainText[BUFFER_FOR_PLAINTEXT + 1] = {0};
DWORD dwPlainSize = 0;
// initalize key and plaintext
StrCpyA((LPSTR)szKey, "00112233445566778899001122334455");
StrCpyA((LPSTR)szIV, "4455667788990011");
StrCpyA((LPSTR)szPlainText, "abcdefghijklmnopqrstuvwxyzabcdef");
// blob data for CryptImportKey() function (include key and version and so on...)
struct AES256KEYBLOB
{
AES256KEYBLOB() { StrCpyA((LPSTR)szBytes, 0); }
BLOBHEADER bhHdr;
DWORD dwKeySize;
BYTE szBytes[DEFAULT_AES_KEY_SIZE + 1];
} AESBlob;
AESBlob.bhHdr.bType = PLAINTEXTKEYBLOB;
AESBlob.bhHdr.bVersion = CUR_BLOB_VERSION;
AESBlob.bhHdr.reserved = 0;
AESBlob.bhHdr.aiKeyAlg = CALG_AES_256;
AESBlob.dwKeySize = DEFAULT_AES_KEY_SIZE;
StrCpyA((LPSTR)AESBlob.szBytes, (LPCSTR)szKey);
// create a cryptographic service provider (CSP)
if(CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
{
if(CryptImportKey(hProv, (BYTE*)&AESBlob, sizeof(AES256KEYBLOB), NULL, CRYPT_EXPORTABLE, &hKey))
{
if(CryptSetKeyParam(hKey, KP_IV, szIV, 0))
{
if(CryptEncrypt(hKey, NULL, TRUE, 0, szPlainText, &dwPlainSize, lstrlenA((LPCSTR)szPlainText) + 1))
{
printf("\nEncrypted data : %s\nSize : %d\n", (LPCSTR)szPlainText, dwPlainSize);
if(CryptDecrypt(hKey, NULL, TRUE, 0, szPlainText, &dwPlainSize)) {
printf("\nDecrypted data : %s\nSize : %d\n", (LPCSTR)szPlainText, dwPlainSize);
}
else
printf("failed to decrypt!");
}
else
printf("failed to encrypt");
}
}
}
Он просто зашифровывает половину открытого текста, а расшифровка еще не завершена! даже изменив просто szPlainText
значение, это всегда дает мне ниже вывод (это означает, что CryptEncrypt()
а также CryptDecrypt()
не работает как положено!)
Encrypted data : U╡π7ÑL|FΩ$}├rUqrstuvwxyzabcdef
Size : 16
Decrypted data : U╡π7ÑL|FΩ$}├rUqrstuvwxyzabcdef
Size : 0
2 ответа
Вот вариант, который я запускаю из VStudio2015.
code.c:
#include <windows.h>
#include <wincrypt.h>
#include <stdio.h>
#include <Shlwapi.h>
#define DEFAULT_AES_KEY_SIZE 32
#define DEFAULT_IV_SIZE 16
#define BUFFER_FOR_PLAINTEXT 32
#define CLEANUP_CRYPT_STUFF(PROV, KEY) \
CryptDestroyKey(KEY); \
CryptReleaseContext(PROV, 0)
#define PRINT_FUNC_ERR_AND_RETURN(FUNC) \
printf("%s (line %d) failed: %d\n", ##FUNC, __LINE__, GetLastError()); \
return -1
typedef struct AES256KEYBLOB_ {
BLOBHEADER bhHdr;
DWORD dwKeySize;
BYTE szBytes[DEFAULT_AES_KEY_SIZE + 1];
} AES256KEYBLOB;
int main() {
// handles for csp and key
HCRYPTPROV hProv = NULL;
HCRYPTKEY hKey = NULL;
BYTE szKey[DEFAULT_AES_KEY_SIZE + 1] = { 0 };
BYTE szIV[DEFAULT_IV_SIZE + 1] = { 0 };
// plain bytes
BYTE szPlainText[BUFFER_FOR_PLAINTEXT + 1] = { 0 }, *pBuf = NULL;
AES256KEYBLOB AESBlob;
memset(&AESBlob, 0, sizeof(AESBlob));
// initalize key and plaintext
StrCpyA((LPSTR)szKey, "00112233445566778899001122334455");
StrCpyA((LPSTR)szIV, "4455667788990011");
StrCpyA((LPSTR)szPlainText, "abcdefghijklmnopqrstuvwxyzabcdef");
DWORD dwPlainSize = lstrlenA((LPCSTR)szPlainText), dwBufSize = dwPlainSize, dwBufSize2 = dwPlainSize;
// blob data for CryptImportKey() function (include key and version and so on...)
AESBlob.bhHdr.bType = PLAINTEXTKEYBLOB;
AESBlob.bhHdr.bVersion = CUR_BLOB_VERSION;
AESBlob.bhHdr.reserved = 0;
AESBlob.bhHdr.aiKeyAlg = CALG_AES_256;
AESBlob.dwKeySize = DEFAULT_AES_KEY_SIZE;
StrCpyA((LPSTR)AESBlob.szBytes, (LPCSTR)szKey);
// create a cryptographic service provider (CSP)
if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
PRINT_FUNC_ERR_AND_RETURN(CryptAcquireContextA);
}
if (!CryptImportKey(hProv, (BYTE*)&AESBlob, sizeof(AES256KEYBLOB), NULL, CRYPT_EXPORTABLE, &hKey)) {
CryptReleaseContext(hProv, 0);
PRINT_FUNC_ERR_AND_RETURN(CryptImportKey);
}
if (!CryptSetKeyParam(hKey, KP_IV, szIV, 0)) {
CLEANUP_CRYPT_STUFF(hProv, hKey);
PRINT_FUNC_ERR_AND_RETURN(CryptSetKeyParam);
}
if (CryptEncrypt(hKey, NULL, TRUE, 0, NULL, &dwBufSize, 0)) {
printf("%d bytes required to hold the encrypted buf\n", dwBufSize);
if ((pBuf = calloc(dwBufSize, sizeof(BYTE))) == NULL)
{
CLEANUP_CRYPT_STUFF(hProv, hKey);
PRINT_FUNC_ERR_AND_RETURN(calloc);
}
StrCpyA(pBuf, szPlainText);
} else {
CLEANUP_CRYPT_STUFF(hProv, hKey);
PRINT_FUNC_ERR_AND_RETURN(CryptEncrypt);
}
if (CryptEncrypt(hKey, NULL, TRUE, 0, pBuf, &dwBufSize2, dwBufSize)) {
printf("\nEncrypted data: [%s]\nSize: %d\n", (LPCSTR)pBuf, dwBufSize2);
if (CryptDecrypt(hKey, NULL, TRUE, 0, pBuf, &dwBufSize)) {
printf("\nDecrypted data: [%s]\nSize: %d\n", (LPCSTR)pBuf, dwBufSize);
} else {
free(pBuf);
CLEANUP_CRYPT_STUFF(hProv, hKey);
PRINT_FUNC_ERR_AND_RETURN(CryptDecrypt);
}
} else {
free(pBuf);
CLEANUP_CRYPT_STUFF(hProv, hKey);
PRINT_FUNC_ERR_AND_RETURN(CryptEncrypt);
}
free(pBuf);
CLEANUP_CRYPT_STUFF(hProv, hKey);
return 0;
}
Примечания:
- Удалены
AES256KEYBLOB
конструктор, как я получил Access Violation (что нормально, когда играешь с памятью "это не твое"). Заменили инициализацию структуры наmemset
вызов Основная (логическая) ошибка заключалась в том, что буфер был недостаточно большим для хранения зашифрованного текста (в сочетании с неправильным значением (0) для
dwPlainSize
- заставить функцию обманчиво преуспеть). Согласно функции [MS.Docs]: CryptEncrypt:Если этот параметр содержит NULL, эта функция рассчитает необходимый размер зашифрованного текста и поместит его в значение, указанное параметром pdwDataLen.
Чтобы узнать необходимый размер, сделайте дополнительный вызов функции, с
pbData
установлен вNULL
(эта практика встречается и в других WinAPI). Затем выделите буфер, заполните его необходимыми данными и сделайте так, чтобы "main" вызвал функцию в этом буфере...Добавлено недостающее
#include
с иmain
- Добавлен код для освобождения используемых ресурсов, когда они больше не нужны
- Refacorings:
- Отрицал
if
условия, так как я не люблю так много уровней вложенности - Добавлены некоторые удобные макросы (
CLEANUP_CRYPT_STUFF
,PRINT_FUNC_ERR_AND_RETURN
), чтобы избежать дублирования кода
- Отрицал
- Другие мелкие исправления / улучшения
- Возможно, вы захотите добавить функцию, которая печатает ровно N байтов из буфера, как
%s
спецификатор останавливается только тогда, когда\0
встречается, и только (немая) удача помешала заполнить консоль мусором (или даже аварийно завершить программу), когдаprintf
буферы - Могут быть некоторые другие аспекты, связанные с этими функциями, с которыми я не справился (поскольку я не являюсь экспертом в этой области), но цель состояла в том, чтобы просто что-то сделать
Выход:
48 bytes required to hold the encrypted buf Encrypted data: [<É╙åh∩φ:bOPs r2w~w╪c╟D╡ï╥V╟neΓßv∩·J8cÅ╥²²²²s] Size: 48 Decrypted data: [abcdefghijklmnopqrstuvwxyzabcdefΓßv∩·J8cÅ╥²²²²s] Size: 32
CristiFati
Ответ велик, и это заставляет меня предложить использование нижеприведенного выражения для расчета длины шифра:
CryptEncrypt(hKey, NULL, TRUE, 0, NULL, &dwBufSize, 0);
Согласно Microsoft Doc:
Если pbData имеет значение NULL, ошибка не возвращается, и функция сохраняет размер зашифрованных данных в байтах в значении DWORD, указанном в pdwDataLen):
BOOL CryptEncrypt(
HCRYPTKEY hKey,
HCRYPTHASH hHash,
BOOL Final,
DWORD dwFlags,
BYTE *pbData,
DWORD *pdwDataLen,
DWORD dwBufLen
);
Мое решение:
Но в моем коде я просто забываю вычислить szPlainText
размер, когда я даю это CryptEncrypt()
:
DWORD dwPlainSize = 0; // initialized with 0
Таким образом, "нулевая длина" не имеет никакого значения для сильфонной функции (CryptEncrypt()
функция всегда получает простой текст с 0
длина):
CryptEncrypt(hKey, NULL, TRUE, 0, szPlainText, &dwPlainSize, lstrlenA((LPCSTR)szPlainText) + 1)
И я должен установить его размер с нижеследующим заявлением (мой code
будет работать, просто добавив это):
dwPlainSize = lstrlenA((LPCSTR)szPlainText);
Тогда передайте это в правильном случае:
CryptEncrypt(hKey, NULL, TRUE, 0, szPlainText, &dwPlainSize, BUFFER_FOR_PLAINTEXT)
Итак, вывод как ниже:
Encrypted data : <É╙åh∩φ:bOPs r2w~w╪c╟D╡ï╥V╟neΓßv∩·J8cÅ╥
Size : 48
Decrypted data : abcdefghijklmnopqrstuvwxyzabcdefΓßv∩·J8cÅ╥
Size : 32