2

I am encrypting the same plain text with AES-128-CFB but ciphertext generated by "Windows Cryptography API: Next Generation" and OpenSSL are different for the same IV, and key.

Plain Text: The quick brown fox jumps over the lazy dog

IV: 1234567887654321

Key: 1234567887654321

Cipher Text generated by Windows CNG in Hex: 279CB0C2FD67B37F33A861CDDAFBEDCEEAEFC68FD5B3478E67E4A936BA7CFE75DDBAD370A1E4D6CDC3455E2F4E188A9C

Cipher Text length generated by Windows CNG: 48 Bytes

Cipher Text generated by OpenSSL in Hex: 279CB0C2FD67B37F33A861CDDAFBEDCEEAEFC68FD5B3478E67E4A936BA7CFE75DDBAD370A1E4D6CDC3455E

Cipher Text length generated by Windows CNG: 43 Bytes

Windows CNG adding extra 5 bytes(2F4E188A9C) at the end, I thought it is due to padding and sent dwFlags value as 0 instead of BCRYPT_BLOCK_PADDING then BCryptEncrypt API call failing with error code 0xc0000206/STATUS_INVALID_BUFFER_SIZE.

Windows CNG code:

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

#define NT_SUCCESS(Status)          (((NTSTATUS)(Status)) >= 0)

#define STATUS_UNSUCCESSFUL         ((NTSTATUS)0xC0000001L)
void
hexdump(const char *msg, const unsigned char *in, unsigned int len)
{
    printf("%s [%d][", msg, len);
    for (; len > 0; len--, in++) {
        printf("%02X", *in);
    }
    printf("]\n");
}
const BYTE rgbPlaintext[] =  "The quick brown fox jumps over the lazy dog";

static const BYTE rgbIV[] = "1234567887654321";

static const BYTE rgbAES128Key[] = "0123456789abcdefghijklmnopqrstuv";

const int ival_len = 16;
const int key_len = 16;
const DWORD block_len = 16;

void __cdecl wmain(
                   int                      argc, 
                   __in_ecount(argc) LPWSTR *wargv)
{

    BCRYPT_ALG_HANDLE       hAesAlg                     = NULL;
    BCRYPT_KEY_HANDLE       hKey                        = NULL;
    NTSTATUS                status                      = STATUS_UNSUCCESSFUL;
    DWORD                   cbCipherText                = 0, 
                            cbData                      = 0, 
                            cbKeyObject                 = 0,
                            cbBlockLen                  = 0,
                            cbBlob                      = 0;
    PBYTE                   pbCipherText                = NULL,
                            pbKeyObject                 = NULL,
                            pbIV                        = NULL,
                            pbBlob                      = NULL;

    UNREFERENCED_PARAMETER(argc);
    UNREFERENCED_PARAMETER(wargv);

    if(!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(
                                                &hAesAlg,
                                                BCRYPT_AES_ALGORITHM,
                                                NULL,
                                                0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptOpenAlgorithmProvider\n", status);
        goto Cleanup;
    }

    if(!NT_SUCCESS(status = BCryptGetProperty(
                                        hAesAlg, 
                                        BCRYPT_OBJECT_LENGTH, 
                                        (PBYTE)&cbKeyObject, 
                                        sizeof(DWORD), 
                                        &cbData, 
                                        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
        goto Cleanup;
    }

    pbKeyObject = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbKeyObject);
    if(NULL == pbKeyObject)
    {
        wprintf(L"**** memory allocation failed\n");
        goto Cleanup;
    }

    if (ival_len > sizeof (rgbIV))
    {
        wprintf (L"**** block length is longer than the provided IV length\n");
        goto Cleanup;
    }

   if(ival_len > 0)
   {       
        pbIV= (PBYTE) HeapAlloc (GetProcessHeap (), 0, ival_len + 1);
        if(NULL == pbIV)
        {
            wprintf(L"**** memory allocation failed\n");
            goto Cleanup;
        }
        strncpy(pbIV,rgbIV,ival_len);
        pbIV[ival_len]='\0';
   }    

    if(!NT_SUCCESS(status = BCryptSetProperty(
                                hAesAlg, 
                                BCRYPT_CHAINING_MODE, 
                                (PBYTE)BCRYPT_CHAIN_MODE_CFB, 
                                sizeof(BCRYPT_CHAIN_MODE_CFB), 
                                0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptSetProperty\n", status);
        goto Cleanup;
    }

    if(!NT_SUCCESS(status = BCryptGetProperty(
                                        hAesAlg, 
                                        BCRYPT_BLOCK_LENGTH, 
                                        (PBYTE)&cbBlockLen, 
                                        sizeof(DWORD), 
                                        &cbData, 
                                        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
        goto Cleanup;
    }

    if(!NT_SUCCESS(status = BCryptGenerateSymmetricKey(
                                        hAesAlg, 
                                        &hKey, 
                                        pbKeyObject, 
                                        cbKeyObject, 
                                        (PBYTE)rgbAES128Key, 
                                        key_len, 
                                        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptGenerateSymmetricKey\n ", status);
        fprintf(stderr, "\nError number <%x>", GetLastError());
        goto Cleanup;
    }
    if (!NT_SUCCESS(status = BCryptSetProperty(hKey,
                                            BCRYPT_MESSAGE_BLOCK_LENGTH,
                                            (PBYTE)&block_len,
                                            sizeof(DWORD),
                                            0))) {
        wprintf(L"**** Error 0x%x returned by BCryptSetProperty\n ", status);
        fprintf(stderr, "\nError number <%x>", GetLastError());
        goto Cleanup;
    }

    printf("\nplan text is [%d][%s]\n", sizeof(rgbPlaintext) - 1, rgbPlaintext);
    if(!NT_SUCCESS(status = BCryptEncrypt(
                                        hKey, 
                                        rgbPlaintext, 
                                        sizeof(rgbPlaintext) - 1,
                                        NULL,
                                        pbIV,
                                        ival_len,
                                        NULL, 
                                        0, 
                                        &cbCipherText, 
                                        BCRYPT_BLOCK_PADDING)))
    {
        wprintf(L"\n**** Error 1 0x%x returned by BCryptEncrypt\n", status);
        goto Cleanup;
    }

    pbCipherText = (PBYTE)HeapAlloc (GetProcessHeap (), 0, cbCipherText);
    if(NULL == pbCipherText)
    {
        wprintf(L"**** memory allocation failed\n");
        goto Cleanup;
    }

    // Use the key to encrypt the plaintext buffer.
    // For block sized messages, block padding will add an extra block.
    if(!NT_SUCCESS(status = BCryptEncrypt(
                                        hKey, 
                                        rgbPlaintext, 
                                        sizeof(rgbPlaintext) - 1,
                                        NULL,
                                        pbIV,
                                        ival_len, 
                                        pbCipherText, 
                                        cbCipherText, 
                                        &cbData, 
                                        BCRYPT_BLOCK_PADDING)))
    {
        wprintf(L"**** Error 2 0x%x returned by BCryptEncrypt\n", status);
        goto Cleanup;
    }

    hexdump("cipher is", pbCipherText, cbCipherText);

Cleanup:

    if(hAesAlg)
    {
        BCryptCloseAlgorithmProvider(hAesAlg,0);
    }

    if (hKey)    
    {
        BCryptDestroyKey(hKey);
    }

    if(pbCipherText)
    {
        HeapFree(GetProcessHeap(), 0, pbCipherText);
    }

    if(pbKeyObject)
    {
        HeapFree(GetProcessHeap(), 0, pbKeyObject);
    }

    if(pbIV)
    {
        HeapFree(GetProcessHeap(), 0, pbIV);
    }
}

OpenSSL Code:

#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <string.h>
void
hexdump(const char *msg, const unsigned char *in, unsigned int len)
{
    printf("%s [%d][", msg, len);
    for (; len > 0; len--, in++) {
        printf("%02X", *in);
    }
    printf("]\n");
}
int main(void)
{
    /* A 256 bit key */
    unsigned char *key = (unsigned char *)"0123456789abcdefghijklmnopqrstuv";
    /* A 128 bit IV */
    unsigned char *iv = (unsigned char *)"1234567887654321";
    /* Message to be encrypted */
    unsigned char *plaintext =
    (unsigned char *)"The quick brown fox jumps over the lazy dog12312";
    unsigned char ciphertext[128];
    int ciphertextLen = encrypt(plaintext, strlen ((char *)plaintext), key, iv, ciphertext);
    hexdump("cipher value is:", ciphertext, ciphertextLen);
    return 0;
}
void handleErrors(void)
{
    ERR_print_errors_fp(stderr);
    abort();
}
int encrypt(unsigned char *plaintext,
int plaintext_len,
unsigned char *key,
unsigned char *iv,
unsigned char *ciphertext)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int ciphertextLen;
    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
    handleErrors();
    
    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cfb(), NULL, key, iv))
    handleErrors();
    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
    handleErrors();
    ciphertextLen = len;
    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
    handleErrors();
    ciphertextLen += len;
    EVP_CIPHER_CTX_free(ctx);
    return ciphertextLen;
}
Pavan
  • 507
  • 1
  • 3
  • 15
  • Uh, that previous comment is not correct [deleted], just use `BCRYPT_PAD_NONE` instead. The message block length in BCRYPT code configures CFB itself, and is correct. **Oh, and you should definitely not ignore the `cbData` result.** The code will not resize your output buffer! – Maarten Bodewes Sep 29 '21 at 12:20
  • @MaartenBodewes used BCRYPT_PAD_NONE in BCryptEncrypt call still same it padding 5 bytes extra to ciphertext. Which cbData result which I am ignoring? – Pavan Sep 29 '21 at 12:56
  • The one in `BCryptEncrypt` that specifies the number of output bytes to the buffer. You don't seem to use it when performing the hex dump. I expect it to be set to 43... – Maarten Bodewes Sep 29 '21 at 14:11
  • @MaartenBodewes cbData from BCryptEncrypt is coming 48 only not 43. – Pavan Sep 29 '21 at 14:24
  • 1
    The [`BCRYPT_MESSAGE_BLOCK_LENGTH`](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-property-identifiers) property controls whether CFB8 (default) or full CFB is used. For CFB8, [`dwFlags`](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptencrypt) equals `0` indeed disables padding. However, in full CFB mode (like in the posted code), disabling results in the posted error `0xc0000206`. Maybe this is a bug. – Topaco Sep 29 '21 at 15:56
  • 1
    Which, however, would have the simple workaround of using only the first n bytes of the ciphertext (with n = plaintext length, which is known). By the way, [`dwFlags`](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptencrypt) should better _not_ be set to `BCRYPT_PAD_NONE`, since this is meant for asymmetric keys (and like `BCRYPT_BLOCK_PADDING` has the value 0x00000001). – Topaco Sep 29 '21 at 16:01
  • @Topaco if we consider plaintext length as ciphertext length by ignoring extra bytes in ciphertext then decryption failing. – Pavan Sep 29 '21 at 16:32
  • 1
    You mean decryption with CNG? That is to be expected, since the padded ciphertext is needed. Here, the following workaround might work: Since for `dwFlags` equals `0` no unpadding is performed, it is probably sufficient to pad the ciphertext (43 bytes) to the next full block (48 bytes) with arbitrary values e.g. 0x00. After that, decryption should be possible. Finally, the resulting plaintext has to be truncated to the length of the original ciphertext (43 bytes). – Topaco Sep 29 '21 at 17:51
  • Wow, if this is a bug then it is a pretty major one. Not that I'm surprised or anything, the crypto API's of Microsoft leave a lot to be desired (and, as you noticed, this includes the documentation). – Maarten Bodewes Sep 29 '21 at 20:39
  • @Topaco if data is segregated in multiple blocks then AES-CFB encryption will create a new IV when the first block is encrypted this new IV will be used for the second block. if we ignore the extra padded block then at the time of decryption it will not create the correct IV so decryption will fail(particularly CNG). We have a requirement to encrypt the data Windows CNG and decrypt it with OpenSSL or Windows CNG and vice versa. – Pavan Sep 30 '21 at 11:21
  • @Topaco if the plaintext is segregated into two variables then it may not work. Please see [This is a link for my code](https://ideone.com/2u0892). Plaintext segregated in two variables rgbPlaintext1 and rgbPlaintext2. I encrypted the first variable rgbPlaintext1 and removed the padding at the end then encrypted the second variable rgbPlaintext2 with the same IV and key arguments used for the first variable encryption and appended the second ciphertext to the first one without padding. I used this ciphertext by adding NULL characters for decryption then decryption failing. – Pavan Sep 30 '21 at 17:01
  • The purpose of the example is not clear to me. What do you want to achieve? Concatenate two CFB ciphertexts, both generated with the **same** IV and key, and then decrypt them in one step? Why should that work and what does it have to do with the original question? To test the original problem, all you need to do is remove the padding on encryption and add arbitrary padding on decryption. Or is this a new question, then this should also be posted as a new question. – Topaco Sep 30 '21 at 18:27

0 Answers0