1
votes

I use openSSL library functions for encrypting and decrypting purposes based on AES-128/CBC.

The code is shown below (do not be confused by the THROW / EXIT macros, they are just gotos). Regardless of what the function returns, you will see printf output to track what is being sent back and forth to OpenSSL:

enc_status_t aes_cipher_ext(uint8_t should_encrypt, enc_aes128_key_t *key, uint8_t *iv, void *in, uint32_t inlen, void *out, uint32_t outlen, uint32_t * outlen_act)

{
    enc_status_t status;
    buffer_t inbuf, outbuf;
    int ok;
    const uint32_t BUFSIZE = AES_BUFFERSIZE;
    uint8_t *read_buf = NULL;
    uint8_t *cipher_buf = NULL;
    uint32_t blocksize;
    int out_len;
    uint32_t numRead;

    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

    /* initialize input read buffer */

    status = buffer_init(&inbuf, in, inlen);
    if (status != ENC_OK) EXIT();
    status = buffer_init(&outbuf, out, outlen);
    if (status != ENC_OK) EXIT();

    /* initialize AES engine */
    ok = EVP_CipherInit(ctx, EVP_aes_128_cbc(), key->data, iv, should_encrypt);

    if(!ok) THROW(status = ENC_ERR_OPENSSL);

    blocksize = EVP_CIPHER_CTX_block_size(ctx);


    read_buf   = malloc(BUFSIZE);
    cipher_buf = malloc(BUFSIZE + blocksize);
    out_len = BUFSIZE + blocksize;

    while (TRUE) {  /*lint !e716 see below */
        /* read data and cipher */
        status = buffer_read(&inbuf, read_buf, BUFSIZE, &numRead);
        if (status != ENC_OK) EXIT();

        printf("AES input: num read = %d\n", numRead);
        dump_ram(read_buf, numRead);

        if (should_encrypt)
        {
            ok = EVP_EncryptUpdate(ctx, cipher_buf, &out_len, read_buf, numRead);
        }
        else
        {
            ok = EVP_DecryptUpdate(ctx, cipher_buf, &out_len, read_buf, numRead);
        }
        status = buffer_write(&outbuf, cipher_buf, out_len);
        printf("AES result bytes: ok=%d\n", ok);
        dump_ram(cipher_buf, out_len);
        if (status != ENC_OK) EXIT();

        if (numRead < BUFSIZE)
        { 
            break; /* this breaks the while */  
        }
    }
    /* handle last block */

    if (should_encrypt)
    {

        ok = EVP_EncryptFinal_ex(ctx, cipher_buf, &out_len);
    }
    else
    {
        ok = EVP_DecryptFinal_ex(ctx, cipher_buf, &out_len);
    }
    printf("AES LAST: ok=%d\n", ok);
    dump_ram(cipher_buf, out_len);
    status = buffer_write(&outbuf, cipher_buf, out_len);
    if (status != ENC_OK) EXIT();


    *outlen_act = outbuf.act_len;


exit_label:
    /* de allocate */
    if (cipher_buf) free(cipher_buf);
    if (read_buf) free(read_buf);
    EVP_CIPHER_CTX_free(ctx);
    return status;

}

When I feed the function with

 Key   = 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 
 IV    = 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
 Plain = 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36

I get the output:

AES input: num read = 16

31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
AES result bytes: ok=1

85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29   <-- first result
AES LAST: ok=1

71 d5 5e 76 23 14 db 09 f6 d8 04 2f d7 5d b6 c9   <-- second result

To be summarized, I get 32 Bytes ciphered text from the 16 bytes plain text:

C = 85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29 71 d5 5e 76 23 14 db 09 f6 d8 04 2f d7 5d b6 c9

Q: Why does openSSL add additional 16 Bytes? IMO there should be no padding in that case...

When decryption is carried out with this 32 Bytes ciphered text, I get, using the same code

AES input: num read = 32

85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29
71 d5 5e 76 23 14 db 09 f6 d8 04 2f d7 5d b6 c9
AES result bytes: ok=1

31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
AES LAST: ok=1

So I get back the original 16 Bytes plain text, with DecryptFinal returning zero bytes (therefore there is no dump).

For comparison, when I decrypt the 32bytes cipered text with this tool the result is

31  32  33  34  35  36  37  38  39  30  31  32  33  34  35  36
10  10  10  10  10  10  10  10  10  10  10  10  10  10  10  10

1   2   3   4   5   6   7   8   9   0   1   2   3   4   5   6
.   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .

So the result is the original plain text (first line) with an additional line of 0x10.

Trying the same tool to encrypt my plain text will give me only a 16 bytes ciphered string, which is the same as the first 16 bytes I get from openSSL:

85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29

Why that? Where is the problem??

1
In C++ its easier to use OpenSSL. You can avoid explicit calls to functions like EVP_CIPHER_CTX_free by using unique_ptr. See EVP Symmetric Encryption and Decryption | C++ Programs on the OpenSSL wiki, unique_ptr and OpenSSL's STACK_OF(X509)*, How to get PKCS7_sign result into a char * or std::string, etc.jww

1 Answers

2
votes

It's for padding. The functions pad the plaintext so that the resulting length is a multiple of the block size (16). Data that is already a multiple of the block is still padded, since otherwise it would be impossible to make the difference between a padding added on purpose, and plaintext that just happens to look like a valid padding.

The usual way of padding is to add N bytes of value N. So either one 0x01 byte , two 0x02 bytes etc., up to sixteen 0x10 bytes for a full block, as in your case.

From the documentation here:

If padding is enabled (the default) then EVP_EncryptFinal_ex() encrypts the "final" data, that is any data that remains in a partial block. It uses standard block padding (aka PKCS padding) as described in the NOTES section, below.

and under NOTES:

PKCS padding works by adding n padding bytes of value n to make the total length of the encrypted data a multiple of the block size. Padding is always added so if the data is already a multiple of the block size n will equal the block size. For example if the block size is 8 and 11 bytes are to be encrypted then 5 padding bytes of value 5 will be added.

But you can control if padding is enabled:

EVP_CIPHER_CTX_set_padding() enables or disables padding. By default encryption operations are padded using standard block padding and the padding is checked and removed when decrypting. If the pad parameter is zero then no padding is performed, the total amount of data encrypted or decrypted must then be a multiple of the block size or an error will occur.