1

I need to encrypt data whose length longer than the block size(128 bits) using windows CryptoApi and aes128 CBC mode.

Iv'e tried multiple ways in order to achive this goal, none worked correctly. I know the theory behind chaining and I theoretically know what I have to do.

  1. split the buffer into block sized blocks
  2. pad if necessary
  3. xor first block with IV sized of block size
  4. encrypt buffer
  5. repeate it - xor the next block with the former

some of above is implemented by CryptoApi(like XORing the first block with the IV) - I red that padding is also being done by the CryptEncrypt when passing TRUE as Final parameter of the function (I may be wrong). I've also tried to pad each block manually using PKCS7 algorithm but it also didn't get the result. AES initialization code (this is where all the initialization of CryptoApi is done, including algorithm specifics such as IV and Key setting):

BOOL AesInitialization()
{

    DWORD dwStatus;
    HCRYPTHASH hHash;
    string FinalIv;

    if (!CryptAcquireContextW(&hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, NULL))
    {
        dwStatus = GetLastError();
        printf("CryptAcquireContext failed: %x\n", dwStatus);
        return FALSE;
    }

    if (!CryptCreateHash(hCryptProv, CALG_SHA_256, 0, 0, &hHash))
    {
        dwStatus = GetLastError();
        printf("CryptCreateHash failed: %x\n", dwStatus);
        clean();
        return FALSE;
    }

    //FinalKey = key;

    if (!CryptHashData(hHash, (BYTE*)key, strlen(key), 0))
    {
        dwStatus = GetLastError();
        printf("CryptHashData Failed : %#x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }

    if (!CryptDeriveKey(hCryptProv, CALG_AES_256, hHash, 0, &hKey))
    {
        dwStatus = GetLastError();
        printf("CryptDeriveKey failed: %x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }

    printf("[+] CryptDeriveKey Success\n");

    DWORD dwMode = CRYPT_MODE_CBC;

// Set the mode to Cipher Block Chaining

    if (!CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&dwMode, 0))
    {
        dwStatus = GetLastError();
        printf("CryptSetKeyParam - setting mode failed: %x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }
// Set the Initialization Vector to ours

    FinalIv =  pad(Iv, AES_BLOCK_SIZE);

    if (!CryptSetKeyParam(hKey, KP_IV, (BYTE *)&FinalIv[0], 0))
    {
        dwStatus = GetLastError();
        printf("CryptSetKeyParam - setting IV failed: %x\n", dwStatus);
        clean();
        CryptDestroyHash(hHash);
        return FALSE;
    }

    CryptDestroyHash(hHash);
    AesStat = TRUE;
    return TRUE;
}

this is the acctual part of code where encryption is done

/*My code has changed several times as I tried many ways to accomplish my 
goal.
My current code(which doesn't include manual padding right now), looks 
like this:*/


BOOL AesEncrypt(string & PlainTxt)
{
    // deviding into blocks sized AES_BLOCK_SIZE
    string cipher;
    string encrypted;
    DWORD block_length;
    unsigned total_length = PlainTxt.length();

    encrypted.resize(AES_BLOCK_SIZE * 2); // give enough space for padding

   for (unsigned index = 0; index < total_length; index += AES_BLOCK_SIZE)
    {
        encrypted.clear(); // clear former content
        memcpy_s(&encrypted[0], encrypted.size(), &PlainTxt[index], AES_BLOCK_SIZE);

        block_length = AES_BLOCK_SIZE;

        if (!CryptEncrypt(hKey, NULL, true, 0, (BYTE *)&encrypted[0], &block_length, encrypted.length()))
        {
            printf("aes failed with %d\n", GetLastError());
            return FALSE;
        }
        cipher.append(encrypted.data()); // append data to final string
    }

    PlainTxt.clear();
    PlainTxt.resize(cipher.size());
    PlainTxt.append(cipher.data());

    return TRUE;
}

The first iteration failed with 234 error(MORE_DATA) and legnth is set by CryptEncrypt to 0x80 (128, in bytes).

jony
  • 41
  • 6
  • Is there no CFB or any other mode included in windows'es crypt api? I wouldn't recommend implementing it on your own. Crypto++ or Botan are well-established C++ crypto libraries I can recommend. Windows'es own API isn't trustworthy (imo), it used to (or still) implements Dual_EC_DRBG. – nada Sep 09 '19 at 08:25
  • Please provide a [mcve] that contains all the information needed to test your code, including how you generate `hKey`. While the code you've posted is certainly wrong (there's no need to pass the data to CryptEncrypt one block at a time; a single call should be enough), I suspect that your _original_ problem may be somewhere else. And since you haven't shown us a full program, we can't really test it. – Ilmari Karonen Sep 09 '19 at 09:01
  • Only set the `final` (third) argument to `CryptEncrypt` to `true` for the last block. – 500 - Internal Server Error Sep 09 '19 at 09:33
  • thank you, I added the thing you requested, thank you for your help in advanced :) @IlmariKaronen – jony Sep 09 '19 at 13:32

1 Answers1

0

I'm pretty sure you don't need to encrypt the data one block at a time. If you have your data in a single buffer, you can just encrypt the whole buffer with a single call to CryptEncrypt, with the Final flag set to true.

The CryptEncrypt function also allows you to encrypt the data in multiple chunks of arbitrary size — as long it's a multiple of the cipher block size — by setting Final to false on all but the last call. This can be useful if you're e.g. trying to encrypt a huge file that is too long to load into memory all at once, but you shouldn't need to use that feature here.

In fact, all you should need to do (after setting the correct cipher mode and IV for hKey) is this:

DWORD dwDataLen = data.length();
data.resize(dwDataLen + AES_BLOCK_SIZE); // give enough space for padding
success = CryptEncrypt(hKey, NULL, TRUE, 0, (BYTE *)&data[0], &dwDataLen, data.length());
data.resize(dwDataLen);  // resize to actual ciphertext length

where data is an std::string that initially contains the plaintext and will be overwritten by the ciphertext.

And, just to prove that this indeed works, here's a full self-contained test program:

#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>

#pragma comment (lib, "advapi32")

using std::string;

char *key = "swordfish";

HCRYPTPROV hCryptProv = NULL; 
HCRYPTKEY hKey = NULL;

#define AES_BLOCK_SIZE 16

BOOL AesInitialization()
{
    char *stage = "n/a";
    BOOL success = TRUE;

    HCRYPTHASH hHash = NULL;

    if (success) {
        stage = "CryptAcquireContext";
        success = CryptAcquireContext(&hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, NULL);
    }
    if (success) {
        stage = "CryptCreateHash";
        success = CryptCreateHash(hCryptProv, CALG_SHA_256, 0, 0, &hHash);
    }
    if (success) {
        stage = "CryptHashData";
        success = CryptHashData(hHash, (BYTE*)key, strlen(key), 0);
    }
    if (success) {
        stage = "CryptDeriveKey";
        success = CryptDeriveKey(hCryptProv, CALG_AES_256, hHash, 0, &hKey);
    }
    if (success) {
        stage = "CryptSetKeyParam";
        DWORD dwMode = CRYPT_MODE_CBC;
        success = CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&dwMode, 0);
    }

    if (!success) printf("AES initialization - %s failed: 0x%08x\n", stage, GetLastError());
    if (hHash != NULL) CryptDestroyHash(hHash);
    return success;
}

BOOL AesEncrypt(string &data, string &iv)
{
    char *stage = "n/a";
    BOOL success = TRUE;
    
    if (success) {
        stage = "CryptGenRandom";
        iv.resize(AES_BLOCK_SIZE);  // allocate space for random IV
        success = CryptGenRandom(hCryptProv, AES_BLOCK_SIZE, (BYTE *)&iv[0]);
    }
    if (success) {
        stage = "CryptSetKeyParam";
        success = CryptSetKeyParam(hKey, KP_IV, (BYTE *)&iv[0], 0);
    }
    if (success) {
        stage = "CryptEncrypt";
        DWORD dwDataLen = data.length();
        data.resize(dwDataLen + AES_BLOCK_SIZE); // give enough space for padding
        success = CryptEncrypt(hKey, NULL, TRUE, 0, (BYTE *)&data[0], &dwDataLen, data.length());
        data.resize(dwDataLen);  // resize to actual ciphertext length
    }

    if (!success) printf("AES encryption - %s failed: 0x%08x\n", stage, GetLastError());
    return success;
}

BOOL AesDecrypt(string &data, string &iv)
{
    char *stage = "n/a";
    BOOL success = TRUE;
    
    if (success) {
        stage = "CryptSetKeyParam";
        iv.resize(AES_BLOCK_SIZE);  // ensure IV has the correct size
        success = CryptSetKeyParam(hKey, KP_IV, (BYTE *)&iv[0], 0);
    }
    if (success) {
        stage = "CryptDecrypt";
        DWORD dwDataLen = data.length();
        success = CryptDecrypt(hKey, NULL, TRUE, 0, (BYTE *)&data[0], &dwDataLen);
        data.resize(dwDataLen);  // resize to actual ciphertext length
    }

    if (!success) printf("AES encryption - %s failed: 0x%08x\n", stage, GetLastError());
    return success;
}

int main()
{
    if (!AesInitialization()) return 1;

    string message = "Hello, World! This is a test of AES-CBC encryption.";
    printf("Plaintext: %s\n\n", message.data());
    
    string iv;
    if (!AesEncrypt(message, iv)) return 1;

    printf("CBC IV (hex):");
    for (unsigned char c : iv) printf(" %02X", c);
    printf("\n");

    printf("Ciphertext (hex):");
    for (unsigned char c : message) printf(" %02X", c);
    printf("\n\n");
    
    if (!AesDecrypt(message, iv)) return 1;

    printf("Decrypted (hex):");
    for (unsigned char c : message) printf(" %02X", c);
    printf("\n");
    printf("Decrypted (text): %s\n", message.data());

    return 0;
}

I won't try to claim any C++ code style awards with this, as I'm not actually particularly familiar with either C++ or with the Microsoft Cryptography API (which, FWIW, is really a plain C API that just happens to be available in C++ as well). But at least it works.

BTW, you should probably pay attention to the note at the top of the CryptEncrypt documentation page:

Important  This API is deprecated. New and existing software should start using Cryptography Next Generation APIs. Microsoft may remove this API in future releases.

Just so you know.

(Also, keep in mind that you probably shouldn't be using plain old CBC mode if you can avoid it. Use an authenticated encryption mode instead — preferably a misuse-resistant one like AES-SIV. If you do want to use CBC, at least apply a message authentication code to the ciphertext after encryption, and verify it before decryption. Besides protecting the integrity of your data, it also protects your encryption code from things like padding oracle attacks.)

Community
  • 1
  • 1
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
  • your code indeed works when I run it seperately with any given input . unfortunately, when I integrate it with my crypto class, it does't work, which probably means that I have a certain problem with the initializtion function that I need to figure. thank you very much for your help ! – jony Sep 10 '19 at 08:38
  • One more question, why don't set the IV in the initialization stub? – jony Sep 10 '19 at 08:45
  • @jony: The IV for CBC mode encryption [must be random](https://crypto.stackexchange.com/questions/3883/why-is-cbc-with-predictable-iv-considered-insecure-against-chosen-plaintext-atta) and different for each message. So you're going to have to change it to a new random value before each encryption anyway. It's easier to just do that in the encryption function. – Ilmari Karonen Sep 10 '19 at 08:48