Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WinAPI - CryptDecrypt() not working properly in AES 256

I used to work with crypto++ in Visual Studio before, but now I want to use of wincrypt.h API functions to encrypt a string with AES 256 with an IV (cbc mode).

I did bellow steps but I'm confused about CryptEncrypt() and CryptDecrypt() functions, because It seems they don't work properly :

  • CryptAcquireContextA defined to create a CSP :

    // create a cryptographic service provider (CSP)
    CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)
    
  • For set key, I'm using of this way (import key):

    CryptImportKey(hProv, (BYTE*)&AESBlob, sizeof(AES256KEYBLOB), NULL, CRYPT_EXPORTABLE, &hKey)
    
  • IV, Key, Plaintext sizes are :

    #define     DEFAULT_AES_KEY_SIZE    32
    #define     DEFAULT_IV_SIZE         16
    #define     BUFFER_FOR_PLAINTEXT    32
    

This is my whole 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");
        }
    }
}

It just encrypt half section of plaintext And decrypting not done! even By changing just szPlainText value, it always give me bellow output (It means that CryptEncrypt() and CryptDecrypt() does not working as expected!) :

Encrypted data : U╡π7ÑL|FΩ$}├rUqrstuvwxyzabcdef
Size : 16

Decrypted data : U╡π7ÑL|FΩ$}├rUqrstuvwxyzabcdef
Size : 0
like image 559

2 Answers

Here's a variant, that I'm running from VStudio 2015.

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;
}

Notes:

  • Removed AES256KEYBLOB constructor as I got Access Violation (which is normal when playing with memory "that's not yours"). Replaced the structure initialization with the memset call
  • The main (logical) error was that the buffer was not large enough to store the encrypted text (combined with a wrong value (0) for dwPlainSize - making the function to misleadingly succeed). According to [MS.Docs]: CryptEncrypt function:

    If this parameter contains NULL, this function will calculate the required size for the ciphertext and place that in the value pointed to by the pdwDataLen parameter.

    To find out the required size, make an additional call to the function, with pbData set to NULL (this practice is also encountered in other WinAPIs). Then allocate a buffer, fill it with required data, and make the "main" call the function on that buffer...

  • Added the missing #includes and main

  • Added code to release the used resources when no longer needed
  • Refacorings:
    • Negated the if conditions, since I don't like so many nesting levels
    • Added some convenience macros (CLEANUP_CRYPT_STUFF, PRINT_FUNC_ERR_AND_RETURN), to avoid code duplication
  • Other minor fixes / improvements
  • You might want to add a function that prints exactly N bytes from a buffer, as "%s" specifier only stops when "\0" is encountered, and only (dumb) luck prevented the console to be filled with garbage (or even the program to crash) when printfing the buffers
  • There might be some other aspects related to this functions that I didn't handle (as I'm not an expert in this area), but the goal was just to have smth working

Output:

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
like image 169
CristiFati Avatar answered Mar 10 '23 09:03

CristiFati


CristiFati answer is great, And it make me offer use of bellow statement for calculate cipher length :

CryptEncrypt(hKey, NULL, TRUE, 0, NULL, &dwBufSize, 0);

According Microsoft Doc :

If pbData is NULL, no error is returned, and the function stores the size of the encrypted data, in bytes, in the DWORD value pointed to by pdwDataLen) :

BOOL CryptEncrypt(
  HCRYPTKEY  hKey,
  HCRYPTHASH hHash,
  BOOL       Final,
  DWORD      dwFlags,
  BYTE       *pbData,
  DWORD      *pdwDataLen,
  DWORD      dwBufLen
);

My Solution :

But in my code, I just forget to calculate szPlainText size when i give it to CryptEncrypt() :

DWORD dwPlainSize = 0;    // initialized with 0

So "zero-length" has not any mean for bellow function (CryptEncrypt() function always get a plain-text with 0 length) :

CryptEncrypt(hKey, NULL, TRUE, 0, szPlainText, &dwPlainSize, lstrlenA((LPCSTR)szPlainText) + 1)

And I should set its size with bellow statement (My code will be worked just by adding this):

dwPlainSize = lstrlenA((LPCSTR)szPlainText);

Then pass it in right case :

CryptEncrypt(hKey, NULL, TRUE, 0, szPlainText, &dwPlainSize, BUFFER_FOR_PLAINTEXT)

So, output is like bellow :

Encrypted data : <É╙åh∩φ:bOPs  r2w~w╪c╟D╡ï╥V╟neΓßv∩·J8cÅ╥
Size : 48

Decrypted data : abcdefghijklmnopqrstuvwxyzabcdefΓßv∩·J8cÅ╥
Size : 32