简体   繁体   中英

How to use openssl EVP library with low-level DES functions?

So I've spent a few hours digging through google search results and some manpages related to openssl functions trying to figure out how to use the EVP functions with 3DES encryption. Right now I have code that is using these functions from <openssl/des.h> :

  • DES_ede2_cbc_encrypt()
  • DES_set_odd_parity()
  • DES_set_key_checked()
  • DES_ecb2_encrypt()

My understanding is that by using the EVP functions, I can uniformly handle encryption/decryption logic between the ECB and CBC modes of 3DES (the first and last functions in my list above). The way the input/output data is managed is different between the two.

I can't find any examples of DES encryption through the high level EVP functions. In the evp.h header, I see things like EVP_des_ede3_ecb and EVP_des_ede3_cbc , but I'm not sure how to use them. Due to the weird way you have to setup the keys for DES (using the 2 functions in the middle from my list above), I'm not sure what loading the keys would look like in the EVP way of things.

How can I use DES with EVP? If someone could provide examples of encryption, decryption, and key setup that would really help. I'm trying to wrap all of this C-like code in C++ and using STL objects/algorithms where I can. And I want my "DES" wrapper to work semantically the same between the different cipher modes of DES. I'm not sure if this is possible, but that's why I'm trying to use EVP.

I think what you want to do should be possible. The EVP API is very much about using all parameters to initialize a context object, and then using the context object without any knowledge of what's inside it. You might even benefit from the fact that OpenSSL will let you supply parameters you don't need...such as supplying an IV (which will be ignored) when initializating a context for ECB mode.

Before going into the EVP version, lets talk a little about the functions you posted.

OpenSSL separates the production of a DES key into three steps:

  1. Make the random bytes
  2. Set/Correct the parity bits within the random bytes
  3. Unroll the key schedule

The first step is done with any suitable (N/D)RBG (OpenSSL offers RAND_DRBG_bytes and RAND_bytes ). The second step arises because some bits of a DES key do not contribute to the cipher strength; hence by convention they are set so as to give the bytes of the key odd parity. This is the function of DES_set_odd_parity (See https://crypto.stackexchange.com/questions/34199/purpose-of-des-parity-bits for more explanation.) The final step has a few options within OpenSSL: (a) trust the user to have given a good key, and unroll that; or (b) check what the user has provided to ensure it's not a weak key, and check that it has odd parity before unrolling the key schedule. The latter of these is performed by the function you list, DES_set_key_checked . The former, more trusting version, is DES_set_key_unchecked .

Okay, so why is all this useful? Well, it all has a direct analog in the EVP version of the API. With the DES functions, you would do the following:

  1. Call RAND_bytes (or equivalent) to make a random key
  2. Call DES_set_odd_parity to set the parity bits
  3. Call DES_set_key_checked to unroll the key schedule
  4. Call DES_<mode>_encrypt to encrypt using the unrolled key schedule

With EVP, you would do the following (mostly) equivalent steps:

  1. Call EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_RAND_KEY, 0, dest_buf) to produce a random key appropriate to the cipher for which EVP_CIPHER_CTX *ctx has been initialized. This step also sets odd parity under the hood via a call to DES_set_odd_parity . Note that dest_buf is assumed to be large enough to hold the type of key that ctx is configured for. See line 280 in https://github.com/openssl/openssl/blob/master/crypto/evp/e_des3.c

  2. Call EVP_EncryptInit passing the key, which calls DES_set_key_unchecked under the hood to unroll the key schedule and store it in the context. Note that this is the unchecked variant of set-key; the EVP API just assumes you're providing a good key. See line 226 in https://github.com/openssl/openssl/blob/master/crypto/evp/e_des3.c (for 2-key ede)

Note the minor chicken-and-egg problem: you have to init the context with the algorithm type, so that it knows what kind of key to make; and you have to supply the key to init the context. EVP_*Init gracefully handles NULL parameters, so it lets you partially initialize. Eg,

uint8_t twokey[16];
EVP_EncryptInit(ctx, EVP_des_ede_cbc(), NULL, NULL);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_RAND_KEY, 0, twokey);
EVP_EncryptInit(ctx, NULL, twokey, iv);

Note also that you don't need OpenSSL to randomly generate the key if you already have one. EVP_EncryptInit is perfectly happy to trust whatever key you provide it.

Here's a rather long example using a hard-coded key, but using the openssl functions to correct its parity.

#include <openssl/evp.h>
#include <openssl/des.h>
#include <stdint.h>

int main(int argc, char *argv[])
{
  int res;
  uint8_t key[16] = {
    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
    0x21, 0x32, 0x43, 0x54, 0x65, 0x76, 0x87, 0x98
  };

  uint8_t iv[8] = {
    0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0
  };

  uint8_t message[16] = {0,1,2,3,4,5,6,7,8,9,0xa,0xb,0xc,0xd,0xe,0xf};
  uint8_t ciphertext[24] = {0}; //leave room for padding!
  int ciphertext_total = 0;
  uint8_t decrypted [16] = {0};
  int decrypted_total = 0;

  //Select our cipher of choice: 2-key 3DES in CBC mode (hence the IV)
  const EVP_CIPHER *tdea_ede = EVP_des_ede_cbc();

  printf("hardcoded key:       ");
  {
    int i;
    for(i=0; i< (int)sizeof(key); i++)
    {
      printf("%02x ", key[i]);
    }
    printf("\n");
  }

  //Note I have to set parity on each of the keys, and I'm doing 2-key 3DES
  //DES_cblock is an annoying typdef of uchar[8]
  DES_set_odd_parity((DES_cblock *)key);
  DES_set_odd_parity((DES_cblock *)(key+8));

  printf("key with odd parity: ");
  {
    int i;
    for(i=0; i< (int)sizeof(key); i++)
    {
      printf("%02x ", key[i]);
    }
    printf("\n");
  }

  printf("Message:             ");
  {
    int i;
    for(i=0; i< (int)sizeof(message); i++)
    {
      printf("%02x ", message[i]);
    }
    printf("\n");
  }

  /* encrypt */
  {
    int outl = 0;
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_EncryptInit(ctx, tdea_ede, key, iv);

    res = EVP_EncryptUpdate(ctx, ciphertext, &outl, message, (int)sizeof(message));
    printf("Update wrote %d bytes\n", outl);
    ciphertext_total += outl;

    EVP_EncryptFinal(ctx, ciphertext + ciphertext_total, &outl);
    printf("Final wrote %d bytes\n", outl);
    ciphertext_total += outl;
  }

  printf("Ciphertext:          ");
  {
    int i;
    for(i=0; i<ciphertext_total; i++)
    {
      printf("%02x ", ciphertext[i]);
    }
    printf("\n");
  }

  /* decrypt */
  {
    int outl = 0;
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_DecryptInit(ctx, tdea_ede, key, iv);

    res = EVP_DecryptUpdate(ctx, decrypted, &outl, ciphertext, ciphertext_total );
    printf("Update wrote %d bytes\n", outl);
    decrypted_total += outl;

    EVP_DecryptFinal(ctx, decrypted + decrypted_total, &outl);
    printf("Final wrote %d bytes\n", outl);
    decrypted_total += outl;
  }

  printf("Decrypted:           ");
  {
    int i;
    for(i=0; i<decrypted_total; i++)
    {
      printf("%02x ", decrypted[i]);
    }
    printf("\n");
  }
}

Its output is:

$ ./a.out
hardcoded key:       11 22 33 44 55 66 77 88 21 32 43 54 65 76 87 98
key with odd parity: 10 23 32 45 54 67 76 89 20 32 43 54 64 76 86 98
Message:             00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
Update wrote 16 bytes
Final wrote 8 bytes
Ciphertext:          5d f9 44 ff 82 0b c3 47 90 be 11 fb 62 01 15 f0 65 45 f6 05 3f fa 81 96
Update wrote 16 bytes
Final wrote 0 bytes
Decrypted:           00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f

I'll admit, I was surprised to see the ciphertext was larger than the message, but then I remembered that OpenSSL applies padding by default.

If you're unfamiliar with the EVP API's init/update/final model, note that it's designed for streams. You can call update as many times as you like, and only once it's got enough data to process a block will you see it write any output. That's why "final" is there...to pad out any remaining data and process the final block if one is in flight.

Also, note that the example leaks the EVP_CIPHER_CTX objects...those should be freed at some point.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM