2

Is it possible, using Windows CNG API and AES in GCM mode, to encrypt a buffer of data with a size that is not a multiple of 16 bytes (128 bits) when chaining is enabled?

When I try to pass a buffer of 60 bytes to the BCryptEncrypt function with chaining enabled, I get the error: 0xc0000206, which translates to the error string: The size of the buffer is invalid for the specified operation.

Here is a code snippet I made to demonstrate my problem:

#include <windows.h>
#include <stdio.h>
#include <bcrypt.h>
#pragma comment(lib, "bcrypt.lib")

int main() {
    unsigned char key[16] = { 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 };
    unsigned char iv[12] = { 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, 0xde, 0xca, 0xf8, 0x88 };
    unsigned char pt[60] = { 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
                         0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
                         0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
                         0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, 0xba, 0x63, 0x7b, 0x39 };
    unsigned char ct[60];
    unsigned char tag[16];

    NTSTATUS bcryptResult = 0;
    DWORD bytesDone = 0;
    BCRYPT_ALG_HANDLE algHandle = 0;
    DWORD blockLength = 16;

    bcryptResult = BCryptOpenAlgorithmProvider(&algHandle, BCRYPT_AES_ALGORITHM, 0, 0);
    bcryptResult = BCryptSetProperty(algHandle, BCRYPT_CHAINING_MODE,(BYTE*)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
    BCRYPT_KEY_HANDLE keyHandle = 0;
    bcryptResult = BCryptGenerateSymmetricKey(algHandle, &keyHandle, 0, 0, key, sizeof(key), 0);

    /* ---Encrypt data--- */
    {
        unsigned char macContext[16];
        unsigned char contextIV[16];

        BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
        BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
        authInfo.pbNonce = iv;
        authInfo.cbNonce = sizeof(iv);
        authInfo.pbTag = tag;
        authInfo.cbTag = sizeof(tag);

        // Enable chaining of BCryptEncrypt calls
        authInfo.pbMacContext = macContext;
        authInfo.cbMacContext = sizeof(macContext);
        authInfo.dwFlags = BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;

        bcryptResult = BCryptEncrypt(keyHandle, pt, sizeof(pt), &authInfo, contextIV, sizeof(contextIV), ct, sizeof(ct), &bytesDone, 0);
        if (!BCRYPT_SUCCESS(bcryptResult)) {
            printf("Error: 0x%x \n", bcryptResult);
        }
    
        // Disable chaining and call BCryptEncrypt again to get tag
        authInfo.dwFlags &= ~BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;
        bcryptResult = BCryptEncrypt(keyHandle, NULL, 0, &authInfo, contextIV, sizeof(contextIV), NULL, 0, &bytesDone, 0);
        if (!BCRYPT_SUCCESS(bcryptResult)) {
            printf("Error: 0x%x", bcryptResult);
        }
    }
    return 0;
}

I have noticed two things.

  1. Encrypting a buffer with a size that is a multiple of 16 bytes works perfectly with the code I have supplied. For example, if changing the buffers pt and ct:s size to 64, everything works fine.
  2. If I do not enable chaining and simply call BCryptEncrypt once with a buffer of size 60 everything works fine as well, and I get the correct tag.

I have not been able to find any information about this in Microsofts CNG documentation. Is this a limitation when using chaining with AES/GCM?

Pineapple
  • 61
  • 1
  • 4
  • I'm not familiar with the specific API used here, but my experience is that most cipher methods require data that is padded to a specific block length. Even if the cipher itself will work on arbitrary-length data, the block-chaining method probably won't. This is a limitation of the math, rather than the implementation. – Kevin Boone Sep 03 '20 at 12:22
  • @KevinBoone GCM is a stream cipher mode and does not require padding to 128-bit block size. Furthermore, even if it did, the question asks why the BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG flag suddenly causes it to require such padding - it doesn't make any sense. If anything, you'd expect that even a mode that DOES require padding will allow you single BCryptEncrypt calls that are not padded to that size, because the entire point of BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG is to allow multiple partial calls with smaller chunks of the plaintext – Omer Tuchfeld Sep 03 '20 at 12:29
  • 1
    Closest thing I could find in the documentation that might indicate that it's impossible is the BCRYPT_BLOCK_PADDING flag - "This flag must not be used with the authenticated encryption modes (AES-CCM and AES-GCM)". Okay, so you don't use it. BUT, they also say "If this flag is not specified, the size of the plaintext specified in the cbInput parameter must be a multiple of the algorithm's block size.". So technically, you didn't specify the flag, so cbInput must be a multiple of block size. The documentation DOES specify it as a limitation. However, it's still weird it works without chaining. – Omer Tuchfeld Sep 03 '20 at 14:08

1 Answers1

0

Assuming that the size you want to pass in is buf_size and buf_size % 16 != 0 then you can:

  1. Call BCryptEncrypt/BCryptDecrypt with the first buf_size - (buf_size % 16) of the buffer using BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG.
  2. Pass in remaining buf_size % 16 when calling BCryptEncrypt/BCryptDecrypt while getting or verifying the tag using pbTag and cbTag without BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG.
Nathan Moinvaziri
  • 5,506
  • 4
  • 29
  • 30