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
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:
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()
.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.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:
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]
.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.