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);
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; }