I've noticed what appears to be a bug in the 586 assembly-optimized 
AES_cbc_encrypt function of OpenSSL 1.0.1e, (compiled on Windows) when 
encrypting data that is> 1 block in length, but not an integral multiple of the 
block size. Specifically it appears that when encrypting the partial-block 
"tail", the block is XOR-ed with the *original* IV passed to AES_cbc_encrypt, 
rather than the previous ciphertext block. This results in incorrect output 
when decrypting.

To test this, I encrypted 40 bytes (2 full blocks plus a half-block "tail") of 
zeros with a 128-bit all-zeros key (key-size does not appear to be a factor but 
provided for reproducability), and all-zeros initial IV. The output is as 
follows:

66 E9 4B D4 EF 8A 2C 3B 88 4C FA 59 CA 34 2B 2E
F7 95 BD 4A 52 E2 9E D7 13 D3 13 FA 20 E9 8D BC
66 E9 4B D4 EF 8A 2C 3B 88 4C FA 59 CA 34 2B 2E

Note that the last ciphertext block is identical to the first ciphertext block, 
which since the plaintext is the same (after the internal zero-padding that 
occurs before encrypting final partial-block) further indicates that it was 
encrypted using the same IV as the first block.

When decrypting this, the final block is corrupt:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
F7 95 BD 4A 52 E2 9E D7 13 D3 13 FA 20 E9 8D BC

If instead the partial-block "tail" is encrypted separately to the full blocks, 
the ciphertext is:

66 E9 4B D4 EF 8A 2C 3B 88 4C FA 59 CA 34 2B 2E
F7 95 BD 4A 52 E2 9E D7 13 D3 13 FA 20 E9 8D BC
A1 0C F6 6D 0F DD F3 40 53 70 B4 BF 8D F5 BF B3

This decrypts to 3 blocks of zeros as expected.

Recompiling without assembly-optimized AES results in the expected 
functionality in both cases.

Reproduce code attached.

Thanks,

CO                                        
#include <stdio.h>
#include <string.h>
#include <openssl/aes.h>

void hexPrint(const void* pv, size_t len)
{
    const unsigned char * p = (const unsigned char*)pv;
    if (NULL == pv)
    {
        printf("NULL");
    }
    else
    {
        for (size_t i = 0; i < len; ++i, ++p)
        {
            if (i && (i % AES_BLOCK_SIZE) == 0) printf("\n");
            printf("%02x ", *p);
        }
    }
    printf("\n");
}

int main(int argc, char **argv)
{
    int keyLength = 128;
    
    unsigned char *aesKey = new unsigned char[keyLength / 8];
    memset(aesKey, 0, keyLength / 8);
    
    unsigned char ivEnc[AES_BLOCK_SIZE];
    unsigned char ivDec[AES_BLOCK_SIZE];

    memset(ivEnc, 0, AES_BLOCK_SIZE);
    memcpy(ivDec, ivEnc, AES_BLOCK_SIZE);
    
    // Will encrypt 2 full blocks plus an additional half block
    size_t inputLength = 2 * AES_BLOCK_SIZE + (AES_BLOCK_SIZE >> 1);
    size_t encLength = 3 * AES_BLOCK_SIZE;
    unsigned char *input = new unsigned char[inputLength];
    unsigned char *encOut = new unsigned char[encLength];
    unsigned char *decOut = new unsigned char[encLength];
    memset(input, 0, inputLength);
    memset(encOut, 0, encLength);
    memset(decOut, 0, encLength);

    AES_KEY encKey;
    AES_KEY decKey;
    AES_set_encrypt_key(aesKey, keyLength, &encKey);
    AES_cbc_encrypt(input, encOut, inputLength, &encKey, ivEnc, AES_ENCRYPT);

    printf("Encrypted:\n");
    hexPrint(encOut, encLength);

    AES_set_decrypt_key(aesKey, keyLength, &decKey);
    AES_cbc_encrypt(encOut, decOut, encLength, &decKey, ivDec, AES_DECRYPT);

    printf("Decrypted:\n");
    hexPrint(decOut, encLength);
    return 0;
}

Reply via email to