copenssl3des

OpenSSL Triple-DES adding incorrect padding


As part of a college assignment, I'm trying to benchmark some of the OpenSSL EVP symmetric ciphers. I've written functions that works perfectly fine on AES, Aria, and Camellia, but after converting the functions to try and use Triple-DES, it refuses to work.

int encrypt(mode m, unsigned char *plaintext, int plaintext_len, unsigned char *key,
            unsigned char *iv, unsigned char *ciphertext)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int ciphertext_len;

    // create and initialise the context
    if(!(ctx = EVP_CIPHER_CTX_new())) {
        handleErrors();
    }

    // initialise the encryption operation based on mode and key length chosen
    switch (m) {
        case CBC:
            if(1 != EVP_EncryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, key, iv)) {
                handleErrors();
            }
            break;
        case ECB:
            if(1 != EVP_EncryptInit_ex(ctx, EVP_des_ede3_ecb(), NULL, key, NULL)) {
                handleErrors();
            }
            break;
    }

    /*
     * provide the message to be encrypted, and obtain the encrypted output.
     * evp_encryptupdate can be called multiple times if necessary
     */
    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) {
        handleErrors();
    }
    ciphertext_len = len;

    /*
     * finalise the encryption. further ciphertext bytes may be written at
     * this stage.
     */
    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) {
        handleErrors();
    }
    ciphertext_len += len;

    /* clean up */
    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}

int decrypt(mode m, unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
            unsigned char *iv, unsigned char *plaintext)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;

    // create and initialise the context
    if(!(ctx = EVP_CIPHER_CTX_new())) {
        handleErrors();
    }

    // initialise the encryption operation based on mode and key length chosen
    switch (m) {
        case CBC:
            if(1 != EVP_DecryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, key, iv)) {
                handleErrors();
            }
            break;
        case ECB:
            if(1 != EVP_DecryptInit_ex(ctx, EVP_des_ede3_ecb(), NULL, key, NULL)) {
                handleErrors();
            }
            break;
    }

    /*
     * provide the message to be decrypted, and obtain the plaintext output.
     * evp_decryptupdate can be called multiple times if necessary.
     */
    if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) {
        handleErrors();
    }
    plaintext_len = len;

    /*
     * finalise the decryption. further plaintext bytes may be written at
     * this stage.
     */
    if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) {
        handleErrors();
    }
    plaintext_len += len;

    /* clean up */
    EVP_CIPHER_CTX_free(ctx);

    return plaintext_len;
}

using the key and initialisation vectors

unsigned char key[24]   = "0123456789abcdef01234567";   // 24-byte key (3 8-byte des keys)
unsigned char iv[8]     = "1234567";                    // 8-byte initialisation vector

when I try to run it, however, it crashes on the line

if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) {
    handleErrors();
}

with the output

407777AEF97F0000:error:1C80006B:Provider routines:ossl_cipher_generic_block_final:wrong final block length:providers/implementations/ciphers/ciphercommon.c:444

So far, I've tried setting the padding variable manually, disabling it, checking to make sure that the length is divisible by eight (it is), and making sure enough space is allocated for the buffers, and no change.

this is on OpenSSL version 3.4.1.


given that this code works for commenters, the error must be how i'm attempting to use it. i've pared down my implementation to a minimum complete verifiable example, and hopefully whatever i'm doing wrong should be easier to spot.

#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

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

    if(!(ctx = EVP_CIPHER_CTX_new())) {
        handleErrors();
    }

    if(1 != EVP_EncryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, key, iv)) {
        handleErrors();
    }

    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) {
        handleErrors();
    }
    ciphertext_len = len;
    
    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) {
        handleErrors();
    }
    ciphertext_len += len;

    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}

int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
            unsigned char *iv, unsigned char *plaintext)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;

    if(!(ctx = EVP_CIPHER_CTX_new())) {
        handleErrors();
    }

    if(1 != EVP_DecryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, key, iv)) {
        handleErrors();
    }

    if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) {
        handleErrors();
    }
    plaintext_len = len;

    if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) {
        handleErrors();
    }
    plaintext_len += len;

    EVP_CIPHER_CTX_free(ctx);

    return plaintext_len;
}

int main (void)
{
    // set up timestamping 
    struct timespec begin, end;

    unsigned char* key    = (unsigned char*) "0123456789abcdef01234567";   // 24-byte key (3 8-byte des keys)
    unsigned char* iv     = (unsigned char*) "1234567";                    // 8-byte initialisation vector

    int decryptedtext_len, ciphertext_len;

    const int LENGTH = 100;
    unsigned char plaintext[LENGTH];
    unsigned char ciphertext[LENGTH + 200];
    unsigned char decryptedtext[LENGTH + 1];

    for(int i = 0; i < LENGTH - 1; i++) {
        plaintext[i] = 'A' + (rand() % 26);
    }
    plaintext[LENGTH - 1] = '\0';

    // encrypt the plaintext
    ciphertext_len = encrypt(plaintext, strlen((char*) plaintext), key, iv, ciphertext);

    printf("ciphertext is:\n");
    BIO_dump_fp (stdout, (const char *)ciphertext, ciphertext_len);

    // decrypt the ciphertext
    decryptedtext_len = decrypt(ciphertext, strlen((char*) ciphertext), key, iv, decryptedtext);

    // add a null terminator. we are expecting printable text
    decryptedtext[decryptedtext_len] = '\0';

    // show the decrypted text
    printf("decrypted text is:\n");
    printf("%s\n", decryptedtext);

    return 0;
}

with output

ciphertext is:
0000 - 88 d9 45 72 c2 0b 12 bb-92 bd e8 8e d2 84 9d 39   ..Er...........9
0010 - d8 df f1 b9 39 a8 66 bb-3c a5 3d 54 e1 fd 46 45   ....9.f.<.=T..FE
0020 - 54 15 d2 cc a0 bd d4 27-96 d6 bb 2e 2d 57 14 25   T......'....-W.%
0030 - e4 07 81 bc 32 c2 2a a5-61 c6 fe b0 dd 0f a6 b4   ....2.*.a.......
0040 - be 29 b6 6d 37 6e ff 23-7d 32 0f 4d 98 91 ce e3   .).m7n.#}2.M....
0050 - 49 fa be 1d 1e 83 05 11-48 e8 dd e4 99 22 6b c8   I.......H...."k.
0060 - 47 c3 06 3e d9 1e 3b 18-                          G..>..;.
4087EFC871700000:error:1C80006B:Provider routines:ossl_cipher_generic_block_final:wrong final block length:providers/implementations/ciphers/ciphercommon.c:444

Solution

  • The problem is not in the implementation of the encrypt() and decrypt() functions, but in the implementation of the calling code and the determination of the parameters used, in particular the lengths.

    main() function of the original code:

    unsigned char* key = (unsigned char*)"0123456789abcdef01234567";   
    unsigned char* iv = (unsigned char*)"1234567";                    
    int decryptedtext_len, ciphertext_len;
    const int LENGTH = 100;
    unsigned char plaintext[LENGTH];
    unsigned char ciphertext[LENGTH + 200];
    unsigned char decryptedtext[LENGTH];
    for(int i = 0; i < LENGTH; i++) {
        plaintext[i] = 'A' + (rand() % 26);
    }
    ciphertext_len = encrypt(plaintext, strlen((char*) plaintext), key, iv, ciphertext);
    decryptedtext_len = decrypt(ciphertext, strlen((char*) ciphertext), key, iv, decryptedtext);
    decryptedtext[decryptedtext_len] = '\0';
    

    the issues were:


    main() function of the revised code:

    unsigned char* key = (unsigned char*)"0123456789abcdef01234567";   
    unsigned char* iv = (unsigned char*)"1234567";                    
    int decryptedtext_len, ciphertext_len;
    const int LENGTH = 100;
    unsigned char plaintext[LENGTH];
    unsigned char ciphertext[LENGTH + 200];
    unsigned char decryptedtext[LENGTH + 1];
    for(int i = 0; i < LENGTH - 1; i++) {
        plaintext[i] = 'A' + (rand() % 26);
    }
    plaintext[LENGTH - 1] = '\0';
    ciphertext_len = encrypt(plaintext, strlen((char*) plaintext), key, iv, ciphertext);
    decryptedtext_len = decrypt(ciphertext, strlen((char*) ciphertext), key, iv, decryptedtext);
    decryptedtext[decryptedtext_len] = '\0';
    

    the issues were:

    Applying these fixes results in:

    unsigned char* key = (unsigned char*)"0123456789abcdef01234567";   
    unsigned char* iv = (unsigned char*)"1234567";                    
    int decryptedtext_len, ciphertext_len;
    const int LENGTH = 100;
    unsigned char plaintext[LENGTH];
    unsigned char ciphertext[LENGTH + 8];
    unsigned char decryptedtext[LENGTH];
    for(int i = 0; i < LENGTH - 1; i++) {
        plaintext[i] = 'A' + (rand() % 26);
    }
    plaintext[LENGTH - 1] = '\0';
    ciphertext_len = encrypt(plaintext, strlen((char*) plaintext), key, iv, ciphertext);
    decryptedtext_len = decrypt(ciphertext, ciphertext_len, key, iv, decryptedtext);
    decryptedtext[decryptedtext_len] = '\0';
    

    Here, the length of the ciphertext is additionally optimized to LENGTH + 8. As in the C code padding is done with PKCS#7 by default, the ciphertext can be larger than the plaintext by a maximum of the block size (8 bytes for 3DES). Alternatively, the ciphertext length can also be determined exactly.