5.22 Performing Low-Level Encryption and Decryption with OpenSSL

5.22.1 Problem

You have set up your cipher and want to perform encryption and decryption.

5.22.2 Solution

Use the following suite of functions:

int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, 
                      unsigned char *in, int inl);
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
                      unsigned char *in, int inl);
int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

5.22.3 Discussion

As a reminder, use a raw mode only if you really know what you're doing. For general-purpose use, we recommend a high-level abstraction, such as that discussed in Recipe 5.16. Additionally, be sure to include some sort of integrity validation whenever encrypting, as we discuss throughout Chapter 6.

The signatures for the encryption and decryption routines are identical, and the actual routines are completely symmetric. Therefore, we'll only discuss the behavior of the encryption functions, and you can infer the behavior of the decryption functions from that.

EVP_EncryptUpdate( ) has the following arguments:

ctx

Pointer to the cipher context previously initialized with EVP_EncryptInit_ex( ).

out

Buffer into which any output is placed.

outl

Pointer to an integer, into which the number of bytes written to the output buffer is placed.

in

Buffer containing the data to be encrypted.

inl

Number of bytes contained in the input buffer.

EVP_EncryptFinal_ex( ) takes the following arguments:

ctx

Pointer to the cipher context previously initialized with EVP_EncryptInit_ex( ).

out

Buffer into which any output is placed.

outl

Pointer to an integer, into which the number of bytes written to the output buffer is placed.

There are two phases to encryption in OpenSSL: update, and finalization. The basic idea behind update mode is that you're feeding in data to encrypt, and if there's incremental output, you get it. Calling the finalization routine lets OpenSSL know that all the data to be encrypted with this current context has already been given to the library. OpenSSL then does any cleanup work necessary, and it will sometimes produce additional output. After a cipher is finalized, you need to reinitialize it if you plan to reuse it, as described in Recipe 5.17.

In CBC and ECB modes, the cipher cannot always encrypt all the plaintext you give it as that plaintext arrives, because it requires block-aligned data to operate. In the finalization phase, those algorithms add padding if appropriate, then yield the remaining output. Note that, because of the internal buffering that can happen in these modes, the output to any single call of EVP_EncryptUpdate( ) or EVP_EncryptFinal_ex( ) can be about a full block larger or smaller than the actual input. If you're encrypting data into a single buffer, you can always avoid overflow if you make the output buffer an entire block bigger than the input buffer. Remember, however, that if padding is turned off (as described in Recipe 5.19), the library will be expecting block-aligned data, and the output will always be the same size as the input.

In OFB and CFB modes, the call to EVP_EncryptUpdate( ) will always return the amount of data you passed in, and EVP_EncryptFinal_ex( ) will never return any data. This is because these modes are stream-based modes that don't require aligned data to operate. Therefore, it is sufficient to call only EVP_EncryptUpdate( ), skipping finalization entirely. Nonetheless, you should always call the finalization function so that the library has the chance to do any internal cleanup that may be necessary. For example, if you're using a cryptographic accelerator, the finalization call essentially gives the hardware license to free up resources for other operations.

These functions all return 1 on success, and 0 on failure. EVP_EncryptFinal_ex( ) will fail if padding is turned off and the data is not block-aligned. EVP_DecryptFinal_ex( ) will fail if the decrypted padding is not in the proper format. Additionally, any of these functions may fail if they are using hardware acceleration and the underlying hardware throws an error. Beyond those problems, they should not fail. Note again that when decrypting, this API has no way of determining whether the data decrypted properly. That is, the data may have been modified in transit; other means are necessary to ensure integrity (i.e., use a MAC, as we discuss throughout Chapter 6).

Here's an example function that, when given an already instantiated cipher context, encrypts an entire plaintext message 100 bytes at a time into a single heap-allocated buffer, which is returned at the end of the function. This example demonstrates how you can perform multiple encryption operations over time and keep encrypting into a single buffer. This code will work properly with any of the OpenSSL-supported cipher modes.

#include <stdlib.h>
#include <openssl/evp.h>
   
/* The integer pointed to by rb receives the number of bytes in the output.
 * Note that the malloced buffer can be realloced right before the return.
 */
char *encrypt_example(EVP_CIPHER_CTX *ctx, char *data, int inl, int *rb) {
  int  i, ol, tmp;
  char *ret;
   
  ol = 0;
  if (!(ret = (char *)malloc(inl + EVP_CIPHER_CTX_block_size(ctx)))) abort(  );
  for (i = 0;  i < inl / 100;  i++) {
    if (!EVP_EncryptUpdate(ctx, &ret[ol], &tmp, &data[ol], 100)) abort(  );
    ol += tmp;
  }
  if (inl % 100) {
    if (!EVP_EncryptUpdate(ctx, &ret[ol], &tmp, &data[ol], inl % 100)) abort(  );
    ol += tmp;
  }
  if (!EVP_EncryptFinal_ex(ctx, &ret[ol], &tmp)) abort(  );
  ol += tmp;
  if (rb) *rb = ol;
  return ret;
}

Here's a simple function for decryption that decrypts an entire message at once:

#include <stdlib.h>
#include <openssl/evp.h>
   
char *decrypt_example(EVP_CIPHER_CTX *ctx, char *ct, int inl) {
  /* We're going to null-terminate the plaintext under the assumption that it's
   * non-null terminated ASCII text.  The null can otherwise be ignored if it
   * wasn't necessary, though the length of the result should be passed back in
   * such a case.
   */
  int  ol;
  char *pt;
   
  if (!(pt = (char *)malloc(inl + EVP_CIPHER_CTX_block_size(ctx) + 1))) abort(  );
  EVP_DecryptUpdate(ctx, pt, &ol, ct, inl);
  if (!ol) { /* There is no data to decrypt */
    free(pt);
    return 0;
  }
  pt[ol] = 0;
  return pt;
}

5.22.4 See Also

Recipe 5.16, Recipe 5.17