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
Другие вопросы по тегам