最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c - OpenSSL Triple-DES adding incorrect padding - Stack Overflow

programmeradmin8浏览0评论

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

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
Share Improve this question edited Mar 23 at 18:36 Progman 19.7k7 gold badges55 silver badges82 bronze badges asked Mar 23 at 14:59 Spartacuz9er9erSpartacuz9er9er 636 bronze badges 18
  • 1 Works for me. Post a complete MCVE including test data and the encrypt()/decrypt() calls. Note that the string is 0-terminated, so that e.g. "12345678" is 9 bytes long (the 8 as length specification gives a compiler error). Fix (or omit) the length specification in the brackets (because of EVP_des_ede3_cbc() the implementation knows that only 8 bytes are to be used for the IV, so that the terminating 0 is ignored). Similarly for the key. Maybe the exception is related to a wronk key/IV specification. – Topaco Commented Mar 23 at 16:54
  • 1 In your first version, the terminating 0 is part of the IV, i.e. the IV is hex encoded 0x3132333435363700. Similarly for the key. If you are aware of this and this is what you want, it is OK. The important thing is that the key and IV are the same for encryption and decryption. – Topaco Commented Mar 23 at 17:22
  • 1 @Topaco: are you sure you're using a C compiler? In C char x[] = "abc"; allocates and initializes 4 chars including the null, while char x[3] = "abc"; removes the null and initializes 3 chars. But in C++ the latter is an error, and char x[2] = "abc"; is an error in both. That said, having a null byte in key, IV, or data is indeed fine if consistent. And the posted code works for me also, using builds from upstream for both 3.3.1 and 3.4.1 (but on a quite old gcc 4.4). OP: how is yours built? Also, EVP pads by default and plain len need not be multiple of 8 (but cipher len is). – dave_thompson_085 Commented Mar 23 at 18:08
  • 1 With these changes, encryption and decryption works for me. Regarding my first comment about unsigned char iv[8] = "12345678"; and a compiler error: You can ignore that. This is because I used a C++ compiler as @dave_thompson_085 pointed out in his comment. – Topaco Commented Mar 23 at 18:27
  • 1 I agree with "use ciphertext_len"; in particular DON'T use strlen(ciphertext) as that is often the wrong value -- either too low (due to embedded null) or too high (due to array being unitialized or adjacent to other storage). Note this applies equally to all ciphers, and if it happened or appeared to work on AES etc that was just (bad) luck. – dave_thompson_085 Commented Mar 23 at 18:45
 |  Show 13 more comments

1 Answer 1

Reset to default 1

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:

  • in the encrypt() call, the plaintext length is determined incorrectly with strlen(). Since the plaintext is defined as an array that is initialized in a loop, there is no terminating 0x00, so strlen() must not be used. Instead, the length can be determined with sizeof().
  • in the decrypt() call, the ciphertext length is determined incorrectly. Since ciphertexts can regularly contain 0x00 values, the length cannot in principle be determined with strlen(). Instead, the length ciphertext_len, which is returned by the encrypt() call must be used here.
  • so that a terminating 0x00 can be set after the regular data in decryptedtext, the memory must be allocated accordingly: unsigned char decryptedtext[LENGTH + 1].

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:

  • the length of the array for the plaintext is not changed, but a terminating 0x00 is set to the last position (so that the plaintext is effectively shortened by 1). With this change, the plaintext length can now be determined with strlen() and it is not necessary to change the size of decryptedtext: unsigned char decryptedtext[LENGTH].
  • in the decrypt() call, the ciphertext length is still determined incorrectly with strlen(). Instead, ciphertext_len must be used as described above.

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.

发布评论

评论列表(0)

  1. 暂无评论