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