So far, we’ve discussed how to use the OpenSSL programmatic interface for securing arbitrary TCP/IP connections using SSL. While SSL is a great general-purpose protocol, there are situations in which it is not appropriate. For example, SSL can’t be used to store encrypted data, such as on a disk or in a cookie, nor can it encrypt UDP traffic. In these cases, you should use the OpenSSL API for symmetric cryptography.
As you have probably noticed, we’ve been careful to recommend using SSL instead of raw cryptographic primitives for securing your applications if at all appropriate. We do this because it is incredibly easy to apply cryptographic primitives in a way that is insecure. Even professional cryptographic protocol designers have a hard time writing “secure” cryptographic protocols built on these primitives, which is one reason peer-review is so important in the world of cryptography.
If you’re planning to use this chapter to do real work, then we assume that you have some sort of need that SSL cannot fill, such as long-term data storage. We recognize that many people will want to design their own network protocols despite our recommendations. If you are considering such an option, we strongly urge you to prefer well-respected protocols, and even pre-existing implementations of those protocols, if possible. Nonetheless, this chapter is a reference for the basic API, and it is your responsibility to use that API in a secure manner.
Although we gave a brief overview of symmetric key cryptography in Chapter 1, there are some additional things we should discuss as background material for the rest of this chapter. Certainly, we don’t wish to serve as a general-purpose textbook on cryptography. For such things, we recommend other books, such as Bruce Schneier’s Applied Cryptography (John Wiley & Sons). For that reason, we’ll avoid any topic that a developer need not care about, such as the internal workings of ciphers. Anything related to the choices you need to make, however, is fair game.
Only two types of symmetric ciphers exist that are well-respected and see any sort of widespread use: block ciphers and stream ciphers. Block ciphers are traditionally the most popular. They operate by breaking up data into fixed-size blocks, and then encrypting each block individually, in which the encryption algorithm is a reversible function of the input. Leftover data is traditionally padded so that the length of the plaintext is a multiple of the cipher’s block size. Stream ciphers are essentially just cryptographic pseudorandom number generators. They use a starting seed as a key to produce a stream of random bits known as the keystream . To encrypt data, one takes the plaintext and simply XORs it with the keystream. Stream ciphers don’t need to be padded per se, though they are usually padded to byte-boundaries, since implementations usually operate on a per-byte level instead of a per-bit level.
The best block ciphers are a far more conservative solution than stream ciphers because they are better studied. Yet stream ciphers tend to be far faster than block ciphers. For example, RC4, currently the most popular stream cipher, runs about 4 times faster than Blowfish, which is among the fastest available block ciphers, and runs almost 15 times faster than 3DES, which is a very conservative cipher choice. AES is faster than 3DES and has more security in terms of a longer key size, but it is still generally slower than even Blowfish.
Neither block ciphers nor stream ciphers can give us perfect security , in which an attacker can never recover a message as long as the communicating parties use the algorithm properly. For each type of cipher, the security is, at best, a function of the key length. It’s always possible to launch a brute-force attack, in which the attacker tries every possible key until the message properly decrypts. If the key length is long enough, the attack will take so long on average as to be infeasible in practice.
Even if there was no better attack on an individual cipher than brute force, there are other issues that plague naive use of both types of cipher. Stream ciphers have the problem that a one-bit flip of the ciphertext causes a one-bit flip in the decrypted plaintext. Obviously, stream ciphers need to be supplemented with data integrity checks. For such purposes, we recommend message authentication codes (MACs—see Chapter 8).
When used directly, block ciphers always encrypt a given block of data in the same way, and thus do not effectively conceal patterns in a stream of data. An attacker can keep a dictionary of known plaintext blocks to known ciphertext blocks, which can often be useful in deciphering real messages. Additionally, an attacker can easily substitute one ciphertext block for another, often with great success. There are ways to use ciphers that can solve these problems to some degree, which we discuss in the next section. Additionally, MACs can be used to thwart actual modification attacks.
Stream ciphers are subject to a similar, more serious problem. Once you start encrypting using a given key, you must continue to generate new data in the keystream, or generate and exchange a new key. If you start over using the same key, the security of the stream cipher is effectively lost. The solution is to never reuse keys when using a stream cipher. Don’t even use the same key across reboots.
OpenSSL implements four common modes for block ciphers. Each of these modes can be used with every block cipher in the library, with the exception of DESX, which is defined as having only a single mode of operation.
ECB (Electronic Code Book) mode is the basic mode of operation, in which the cipher takes a single block of plaintext and produces a single block of ciphertext. Data streams are broken into blocks that are individually processed. Usually, this mode is padded to accommodate messages that aren’t a multiple of the cipher’s block size length (in fact, you cannot avoid padding in OpenSSL prior to 0.9.7). Because of padding, the ciphertext can be up to a block longer than the plaintext. In addition, as previously mentioned, this mode is highly susceptible to dictionary attacks. ECB is almost always the wrong mode for the job, because it is so difficult to use securely. We strongly recommend that you not use it under any circumstances, unless you really know what you’re doing. The biggest advantage of ECB over the other common modes is that messages can be encrypted in parallel. However, this is not an adequate reason to use ECB—an alternative mode that allows for parallelization is counter mode, which we discuss later in this chapter.
CBC (Cipher Block Chaining) mode essentially solves ECB’s dictionary problem by XORing the ciphertext of one block with the plaintext of the next block. Since block ciphertexts are interdependent, parallelization isn’t possible. CBC is still a block-based mode, meaning that padding is generally used.
CBC mode can be used to encrypt multiple data streams. However, dictionary attacks are possible if the data streams have common beginning sequences. For that reason, it is possible to set an initialization vector (IV), which is a block of data that gets XOR’d with the first block of plaintext before encrypting that block. The value of the IV need not be secret, but it should be random. The IV must be available to properly decrypt the ciphertext.
CFB (Cipher Feedback) mode is one way of turning a block cipher into a stream cipher, though a complete block of plaintext must be received before encryption can begin. This mode isn’t as prone to data manipulation attacks as most stream ciphers. Like CBC mode, CFB mode can use an initialization vector. The IV is more important than in CBC mode, because if two data streams are encrypted with the same key, and have the same IV, then both streams can be recovered. In practice, avoid reusing the same key when using CFB mode.
OFB (Output Feedback) mode is another way of turning a block cipher into a stream cipher. OFB mode works more like a traditional stream cipher than CFB mode, and is therefore more susceptible to the same kind of bit-flipping attacks that affect stream ciphers (generally not a problem if you use a message authentication code). A compelling feature of OFB mode is that most of the work can be done offline. That is, you can generate a keystream before there is even data available to encrypt, while you have spare CPU cycles. The plaintext simply gets XOR’d into the keystream. OpenSSL doesn’t directly support keystream precomputation. OFB mode can also use an IV. As with CBC mode, avoid using the same key to encrypt multiple data streams, particularly if you always use the same IV.
The OpenSSL API for symmetric cryptography is vast. Each cipher has its own set of routines for encryption and decryption. Fortunately, OpenSSL also provides a single API that serves as an interface to all symmetric encryption algorithms: the EVP interface, which can be accessed by including openssl/evp.h. The EVP API provides an interface to every cipher OpenSSL exports. Before using the EVP interface, we must know how to get a reference to the different ciphers we may wish to use. OpenSSL represents ciphers as data objects that generally get loaded behind the programmer’s back. When you wish to use a particular cipher, you simply request a reference to the object associated with that cipher. There are two common methods for doing this. First, OpenSSL provides a method for each cipher in each mode of interest for that cipher, which loads the cipher data object into memory if necessary. For example, we can use the following code to get a reference to the Blowfish-CBC cipher object:
EVP_CIPHER *c = EVP_bf_cbc( );
Second, OpenSSL provides the function
EVP_get_cipherbyname
, which returns the appropriate cipher object
given a string representation of the cipher configuration, or
NULL if no matching cipher is found. This function
can be used only on cipher configurations that have previously been
loaded. You can load all symmetric ciphers with the call
OpenSSL_add_all_ciphers
, which takes no parameters.
OpenSSL_add_all_algorithms
will also do the trick, but will load other
kinds of cryptographic algorithms. The problem with using these
functions is that they cause all ciphers to be linked into an
executable at runtime, even when using dynamic loading. To avoid this
overhead, avoid those calls, and manually add ciphers that you wish
to use. If you want to add a small set of ciphers that you can then
look up by name based on dynamic information, you can use
EVP_add_cipher
. For example:
EVP_add_cipher(EVP_bf_cbc( ));
OpenSSL provides implementations of an array of algorithms that meet most needs. The only significant lack in some versions is an implementation of the new Advanced Encryption Standard (AES). AES is supported in the long-awaited 0.9.7 release.
In addition to the actual cipher algorithms that OpenSSL provides,
the null cipher is also supported, which passes data through
untouched. You can access this cipher using
EVP_enc_null. It is primarily useful for testing
the EVP interface, so you should generally avoid using it in
production systems.
AES is the new Advanced Encryption Standard, also occasionally called Rijndael. It is available only in OpenSSL Versions 0.9.7 or later. AES is a block cipher that supports key and block sizes of 128, 192, and 256 bits. Unfortunately, as of this writing, OpenSSL does not provide support for using AES in CFB or OFB modes. See Table 6-1.
|
Cipher mode |
Key/block size |
EVP call for cipher object |
String for cipher lookup |
|
ECB |
128 bits |
|
|
|
CBC |
128 bits |
|
|
|
ECB |
192 bits |
|
|
|
CBC |
192 bits |
|
|
|
ECB |
256 bits |
|
|
|
CBC |
256 bits |
|
|
Blowfish is a block cipher designed by Bruce Schneier of Applied Cryptography fame. This algorithm has a good security margin and is the fastest block cipher OpenSSL provides. The key length of Blowfish is variable (up to 448 bits), but generally, 128-bit keys are used. The block-size for this cipher is fixed at 64-bits. Its biggest drawback is that key setup time is slow. As a result, Blowfish isn’t a good choice when many different keys are used to encrypt short data items. Table 6-2 gives details.
The CAST5 algorithm, authored by Carlisle Adams and Stafford Tavares, is another cipher with variable-length keys and 64-bit blocks. The CAST5 specification allows for key lengths between 5 and 16 bytes (40 and 128 bits; keys must be a multiple of 8 bits in length). OpenSSL defaults to using 128-bit keys. CAST is a fast cipher with no known weaknesses. See Table 6-3.
DES, the Data Encryption Standard, uses fixed 64-bit blocks and 64-bit keys. Eight bits are parity bits, giving a maximum of 56 bits of strength. These days, the parity bits are usually completely ignored. DES dates back to the mid-1970s and is certainly the most widely scrutinized symmetric algorithm available. While no significant attacks better than brute force have ever been found, brute force is a very real attack, since a 56-bit keyspace is widely considered too small. Additionally, DES is the slowest of the ciphers OpenSSL supports, except for more secure DES variants. It is a good idea to avoid vanilla DES unless you are supporting legacy systems. See Table 6-4.
DESX is a DES variant that is resistant to brute-force attacks. It uses an additional 64 bits of key material to obscure the inputs and outputs of DES. The extra key material is used in a simple and efficient manner, resulting in a cipher that is not much slower than traditional DES, but is far more resistant to brute-force attacks. In fact, a brute-force attack is infeasible with DESX without a large number of known plaintexts. Other attacks against DESX may worry you if you think an attacker might be able to get 260 plaintext/ciphertext pairs. Usually, that’s not much of a worry. DESX runs only in CBC mode.
When speed is important and cryptographic acceleration is an option, DESX shines, because most such hardware supports DES (often exclusively), and DESX can be accelerated using standard DES acceleration. Nonetheless, triple DES offers a greater security margin, so is preferable if its performance can be tolerated. See Table 6-5 for details.
Triple DES , often written as 3DES, is the most popular variant of DES and is probably the most conservative symmetric cipher available, due to the wide scrutiny DES has seen in the past quarter century. It is also the slowest algorithm available, though acceleration hardware can help. With 3DES, encryption is performed by encrypting data using DES, “decrypting” the ciphertext using a second key, then encrypting the data again, either with the original key (two-key 3DES) or with a third key (three-key 3DES). Three-key 3DES is always a better choice than two-key, as it is more secure and is no slower. The only drawback is that it requires a few extra bits for storing the additional key material. See Table 6-6.
|
Cipher mode |
EVP call for cipher object |
String for cipher lookup |
|
ECB (3 key) |
|
|
|
CBC (3 key) |
|
|
|
CFB (3 key) |
|
|
|
OFB (3 key) |
|
|
|
ECB (2 key) |
|
|
|
CBC (2 key) |
|
|
|
CFB (2 key) |
|
|
|
OFB (2 key) |
|
|
The IDEA cipher is a good all-around block cipher with 128-bit keys and 64-bit blocks. It is fast and is widely regarded as strong. Its major drawback is that it is covered by patent in the U.S. and Europe. Nonetheless, you can use the algorithm without paying a fee for noncommercial purposes.
IDEA is about 10 years old and has seen a fair amount of scrutiny. Bruce Schneier highly recommends the algorithm in Applied Cryptography, and it is commonly used with PGP. Table 6-7 gives more information.
The RC2 algorithm is a block cipher from RSA Labs.
RC2 supports variable-length keys up to 128 bytes. OpenSSL’s implementation uses a default length of 16 bytes (128 bits). There’s an additional parameter for setting the “effective” key strength. What this means is you can take, say, a 128-bit key and cripple it to 40 bits worth of security. We strongly recommend against using this parameter.
RC2 is efficient and has no significant published weaknesses. However, the algorithm has not really seen a great deal of scrutiny, particularly compared to DES and AES. Table 6-8 gives the details.
RC4 is a stream cipher with variable-length keys that can be up to 256 bytes long. RC4 was previously a trade secret but is now in common use due to the publication of a reverse-engineered, third-party implementation. If you use RC4 in a commercial product, RSA Security might come after you legally, even though it would be unlikely to win. The name RC4 is also trademarked, and you should consult RSA Security before using it.
RC4 is a stream cipher and is blazingly fast compared to the available block ciphers in OpenSSL. It’s certainly the fastest algorithm currently implemented in OpenSSL. RC4 is also well-regarded as an algorithm. For this reason, and due to its widespread use in SSL, it’s vastly popular, though it is widely used with insecure 40-bit keys.
RC4 is difficult to use well. The encryption algorithm itself is good, but some problems with the way it sets up keys require care in using it. In particular, RSA Security recommends you take one of the following two steps when using this algorithm:
Make sure that all key material is cryptographically hashed before use. The problem necessitating this solution is most prominent when frequently rekeying RC4. A common approach to frequent rekeying is to use a base key, and then concatenate with a counter. In RC4, that turns out to be a bad thing to do. If you take the key material and the counter and hash them together to get the actual key, the weakness goes away. The general recommendation of hashing all key material before use is a good one, no matter which cipher you use in your applications.
Discard the first 256 bytes of the generated key stream before using it. The easy way to do this is to encrypt 256 bytes of random data and discard the results.
Additionally, as previously noted, it is particularly important to supplement use of RC4 with a MAC to ensure data integrity. See Table 6-9 for more information.
RC5 is another block cipher from RSA Security. Its name is trademarked, and its algorithm is covered by an issued patent. You should certainly contact RSA Security before using this algorithm in any application.
RC5 is interesting because it is fast, well-regarded, and highly customizable. According to the RC5 specification, you can choose 64- or 128-bit blocks, use keys up to 255 bytes in size, and can use any number of cipher rounds, up to 255. However, OpenSSL’s implementation uses 64-bit blocks and limits rounds to 8, 12, or 16, defaulting to 12. See Table 6-10.
|
Cipher mode |
Key bits |
Rounds |
EVP call for cipher object |
String for cipher lookup |
|
ECB |
128 |
12 |
|
|
|
CBC |
128 |
12 |
|
|
|
CFB |
128 |
12 |
|
|
|
OFB |
128 |
12 |
|
|
Note that nondefault parameters to RC5 cannot currently be accessed through EVP calls or through cipher lookup by name. Instead, you must first reference the default RC5 cipher object in the correct mode, and then use other calls to set parameters, as described below.
Before we can begin encrypting or decrypting, we must allocate and initialize a cipher context. The cipher context is a data structure that keeps track of all relevant state for the purposes of encrypting or decrypting data over a period of time. For example, we can have multiple streams of data encrypted in CBC mode. The cipher context will keep track of the key associated with each stream and the internal state that needs to be kept between messages for CBC mode. Additionally, when encrypting with a block-based cipher mode, the context object buffers data that doesn’t exactly align to the block size until more data arrives, or until the buffer is explicitly flushed, at which point the data is usually padded as appropriate.[1]
The generic cipher context type is
EVP_CIPHER_CTX
.
We can initialize one, whether it was allocated dynamically or
statically, by calling
EVP_CIPHER_CTX_init
, like so:
EVP_CIPHER_CTX *x = (EVP_CIPHER_CTX *)malloc(sizeof(EVP_CIPHER_CTX)); EVP_CIPHER_CTX_init(x);
After allocating the context object and initializing it, we must set up the cipher context. At this point, we generally determine whether the particular context will be used for encrypting or decrypting. It is possible to set up a context to do both, but it’s a bad idea in any mode other than ECB mode, because race conditions can easily occur that will desynchronize communication. Essentially, the internal cipher state of the two communicating parties needs to remain synchronized at all times. If both parties send data at the same time, they will likely end up trying to decrypt the data they receive using an incorrect state.
When we set up a cipher context, we not only choose whether we’re encrypting or decrypting, but also do the following:
Choose the type of cipher we will be using, including the mode in
which to use that cipher. We will be passing an
EVP_CIPHER object to an initialization routine.
Set the key to be used for operations by passing it to the initialization routine as an array of bytes.
Specify an initialization vector for the cipher, if appropriate for the mode. A default IV will be used if not otherwise specified.
If using the “engine” release, we
can specify whether we want to use hardware acceleration, if
available. If we do, we must have previously specified an
“engine” to use that supports our
hardware. Specifying NULL as an engine tells
OpenSSL to use its default software implementations. In 0.9.7, this
functionality will be part of the library proper.
EVP_EncryptInit
is the preferred method for setting up a
cipher context for encryption. For decryption, it is
EVP_DecryptInit
. Both of these methods have the same
signature, which includes four parameters.
int EVP_EncryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
unsigned char *key, unsigned char *iv);
int EVP_DecryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
unsigned char *key, unsigned char *iv);ctx
The EVP cipher context object to use.
type
The cipher to use.
key
The key to use for encrypting or decrypting.
iv
The initialization vector to use.
Notice that the engine package prefers an extended API,
EVP_EncryptInit_ex and
EVP_DecryptInit_ex, which inserts a fifth argument
before the key that is a pointer to the engine to use; it should be
NULL when no hardware acceleration is being used.
When Version 0.9.7 of OpenSSL is released, these versions of the
calls will be the preferred API. When using engines, many calls can
fail, so check error codes. We don’t do this because
we don’t use the ENGINE API in our examples.
Let’s consider an example in which we try to encrypt
using Blowfish with 128-bit keys in CBC mode. CBC mode can use an
initialization vector, which is always the size of one block (in this
case, 8 bytes). We will use the OpenSSL pseudorandom number generator
to provide a randomly generated key; however, distributing that key
is not really covered in this example. For now,
we’ll assume that you will do it offline, perhaps by
exchanging a disk, or reading the key over the phone (key exchange
protocols are discussed in Chapter 8).
To that end, we do print out the key to
stdout in hexadecimal format. Note that doing this
is not really the best idea for real applications. Example 6-1 shows how to do this.
#include <openssl/evp.h>
void select_random_key(char *key, int b)
{
int i;
RAND_bytes(key, b);
for (i = 0; i < b - 1; i++)
printf("%02X:", key[i]);
printf("%02X\n", key[b - 1]);
}
void select_random_iv(char *iv, int b)
{
RAND_pseudo_bytes(iv, b);
}
int setup_for_encryption(void)
{
EVP_CIPHER_CTX ctx;
char key[EVP_MAX_KEY_LENGTH];
char iv[EVP_MAX_IV_LENGTH];
if (!seed_prng( ))
return 0;
select_random_key(key, EVP_MAX_KEY_LENGTH);
select_random_iv(iv, EVP_MAX_IV_LENGTH);
EVP_EncryptInit(&ctx, EVP_bf_cbc( ), key, iv);
return 1;
}Note that multiple implementations of the
seed_prng function are provided in Chapter 4. It returns 0 if the pseudorandom number
generator cannot be seeded securely. We return an error status from
our setup function in this case, so we don’t need to
check the return value of RAND_pseudo_bytes when
we call it. Also, you may want to use raw entropy. See Chapter 4 for more information.
Another thing to note is that the party decrypting the data will need
to initialize its cipher context with the same
initialization vector created
here. Passing the initialization vector in the clear is OK, but it
should probably be MAC’d so that the receiver can
detect tampering. If NULL is passed in for an IV,
an array filled with zeros is used. Note that IVs can be used in all
modes except ECB. In ECB mode, you can still pass in an IV, but block
ciphers will ignore it.
Setting up for decryption is generally easier, because we already know the key and the IV used. Example 6-2 shows how to set up for decryption under the same configuration.
#include <openssl/evp.h>
void setup_for_decryption(char *key, char *iv)
{
EVP_CIPHER_CTX ctx;
EVP_DecryptInit(&ctx, EVP_bf_cbc( ), key, iv);
}Subsequent calls to EVP_EncryptInit or
EVP_DecryptInit will change the value of any
non-null parameter as long as the cipher type parameter is set to
NULL. Otherwise, the context is completely
reinitialized. Additionally, the key and the IV can both be set to
NULL on the first call to these functions, and set
separately later. This is necessary when you specify a cipher and
then change the key length from the default. Of course, you will need
to at least provide a valid key before encryption begins.
Many
ciphers take a variable key length, which can be easily set after
initialization using the call
EVP_CIPHER_CTX_set_key_length
. For example, we can set the Blowfish key
length to 64 bits, as follows:
EVP_EncryptInit(&ctx, EVP_bf_ecb( ), NULL, NULL); EVP_CIPHER_CTX_set_key_length(&ctx, 8); EVP_EncryptInit(&ctx, NULL, key, NULL);
In this case, we set the key with a second call to
EVP_EncryptInit after we specify the key length.
When using this functionality, make sure you only set the key length to a valid value for the cipher.
If we wish to check the
default key length of a cipher
object, we can use the call
EVP_CIPHER_key_length
. For example, the following will show us the
default key length for Blowfish:
printf("%d\n", EVP_CIPHER_key_length(EVP_bf_ecb( ));We can also check to see the length of the keys a cipher context is using:
printf("%d\n", EVP_CIPHER_CTX_key_length(&ctx));For other cipher parameters, OpenSSL provides a generic call,
EVP_CIPHER_CTX_ctrl
. Currently, this call can only set or query
the effective key strength in RC2 or the number of rounds used in
RC5.
int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr);
ctx
The cipher context object.
type
The operation to perform, which can be one of the following constants:
EVP_CTRL_GET_RC2_KEY_BITS
EVP_CTRL_SET_RC2_KEY_BITS
EVP_CTRL_GET_RC5_ROUNDS
EVP_CTRL_SET_RC5_ROUNDS
arg
The numerical value to set, if appropriate. If not appropriate, its value is ignored.
ptr
A pointer to an integer for querying the numerical value of a property.
For example, to query the effective key bits in an RC2 cipher
context, storing the result in a variable called
kb:
EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GET_RC2_KEY_BITS, 0, &kb);
And to set the effective key strength of RC2 to 64 bits:
EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_SET_RC2_KEY_BITS, 64, NULL);
Setting and querying RC5 rounds works the same way. Remember from our previous discussion that OpenSSL is limited to 8, 12, or 16 rounds for RC5.
Another desirable option to set in a
cipher context is whether padding is used.
Without padding, the size of the ciphertext will always be the same
size as the plaintext. On the downside, the length of the data
encrypted must be an exact multiple of the block size. With padding,
any length in bytes is feasible, but the resulting ciphertext can be
up to a block longer than the plaintext. Unfortunately, OpenSSL
versions through 0.9.6c do not allow padding to be disabled. This
changes in Version 0.9.7, which has a function called
EVP_CIPHER_CTX_set_padding that takes a pointer to
a cipher context, and an integer that represents a Boolean value (0
for no padding, 1 for padding).
Once the c ipher context is initialized, there are two steps to encryption with the EVP interface: updating and finalization. When you have data to encrypt, you pass the data to the update function, along with a pointer to where you’d like any output to go. There may or may not be output as the result of an update. If the cipher can encrypt one or more entire blocks of data, it will do so. Any leftover data will be buffered and processed either during the next call to the update function or during the call to finalize. When calling finalize, any leftover data is padded and encrypted. If there is no leftover data, a block of pad is encrypted.[2] As with updating, you must tell the routine where to store resulting data.
The update function is
EVP_EncryptUpdate
.
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
unsigned char *in, int inl);ctx
The cipher context to use.
out
A buffer that will receive the encrypted data.
outl
Receives the number of bytes written to the encrypted data buffer.
in
A buffer that contains the data to be encrypted.
inl
Specifies the number of bytes contained in the input data buffer.
When using a block-based cipher mode (ECB or CBC), the amount of output written can be both larger and smaller than the length of the input, due to internal buffering and padding. If you’re using a cipher with 8-byte (64-bit) blocks, the output could be up to 7 bytes smaller than the input, or up to 7 bytes larger.[3] If you are encrypting incrementally with a single key, and are producing packets of data, this is good to keep in mind. If, instead, you’re encrypting to a single buffer, you will always avoid overflow by making the output buffer an entire block bigger than the input buffer (the extra block may fill with padding). Optionally, you can manually keep track of exactly how much data will be output as a function of how much data was input.
The finalization function is
EVP_EncryptFinal
.
int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
ctx
The cipher context to use.
out
A buffer that will receive the encrypted data.
outl
Receives the number of bytes written to the encrypted data buffer.
Currently, this function always outputs. However, in the forthcoming
0.9.7 release it will not place anything in the output buffer at all
if padding is turned off. In such a case, if there is any buffered
data, the function returns an error. Additionally,Version 0.9.7 adds
an
EVP_EncryptFinal_ex
call that should be used when a context has
been initialized by EVP_EncryptInit_ex.
Example 6-3 shows the implementation of a function that takes a pointer to an initialized EVP cipher context, a buffer to encrypt, an associated length, and a pointer to an integer. The function then encrypts the data 100 bytes at a time into a heap-allocated buffer, which is the function’s return value. The length of the resulting ciphertext is passed back in the address specified by the final parameter.
#include <openssl/evp.h>
char *encrypt_example(EVP_CIPHER_CTX *ctx, char *data, int inl, int *rb)
{
char *ret;
int i, tmp, ol;
ol = 0;
ret = (char *)malloc(inl + EVP_CIPHER_CTX_block_size(ctx));
for (i = 0; i < inl / 100; i++)
{
EVP_EncryptUpdate(ctx, &ret[ol], &tmp, &data[ol], 100);
ol += tmp;
}
if (inl % 100)
{
EVP_EncryptUpdate(ctx, &ret[ol], &tmp, &data[ol], inl%100);
ol += tmp;
}
EVP_EncryptFinal(ctx, &ret[ol], &tmp);
*rb = ol + tmp;
return ret;
}Factoring in the block length (done by calling
EVP_CIPHER_CTX_block_size) is unnecessary when
using a stream cipher, or when using the CFB or OFB cipher modes,
since there is no padding in those cases. As a result, the cipher can
output encrypted data as needed, without having to buffer any
plaintext.[4]
The above example works well when
we can afford to encrypt everything into a single buffer before
processing the ciphertext. It doesn’t work so well
if we need to deal with ciphertext incrementally. For example, we
might wish to send blocks of data as quickly as possible, and not
wait for all data to be processed. Example 6-4 shows
a solution for such a scenario. Data to be encrypted is sent to
incremental_encrypt
as needed. When there’s data
to be sent, incremental_encrypt calls
incremental_send
, which is simply a stub, but it can place
those blocks currently encrypted on the network. When all data to be
encrypted has been passed to incremental_encrypt,
then
incremental_finish
is called.
#include <openssl/evp.h>
int incremental_encrypt(EVP_CIPHER_CTX *ctx, char *data, int inl)
{
char *buf;
int ol;
int bl = EVP_CIPHER_CTX_block_size(ctx);
/* Up to the block size - 1 chars can be buffered up. Add that to the length
* of the input, and then we can easily determine the maximum number of
* blocks output will take by integer divison with the block size.
*/
buf = (char *)malloc((inl + bl - 1) / bl * bl);
EVP_EncryptUpdate(ctx, buf, &ol, data, inl);
if (ol)
incremental_send(buf, ol);
/* incremental_send must copy if it wants to store. */
free(buf);
return ol;
}
/* Also returns the number of bytes written. */
int incremental_finish(EVP_CIPHER_CTX *ctx)
{
char *buf;
int ol;
buf = (char *)malloc(EVP_CIPHER_CTX_block_size(ctx));
EVP_EncryptFinal(ctx, buf, &ol);
if (ol)
incremental_send(buf, ol);
free(buf);
return ol;
}Note that the number of bytes written by
EVP_EncryptFinal should always be 8 when using
64-bit blocks and when padding is enabled.
As
expected, the decryption API looks similar to the encryption API.
After cipher initialization, two methods are involved,
EVP_DecryptUpdate
and
EVP_DecryptFinal
. You can pass as much data as you want into
EVP_DecryptUpdate.
When using a block-based mode (ECB or CBC), you can pass in partial
blocks of text, but EVP_DecryptUpdate will output
only whole blocks. The rest will be stored until there is more data
to be processed, or until EVP_DecryptFinal is
called. Moreover, if the context’s cached ciphertext
plus the length of the new ciphertext is exactly block-aligned, the
entire final block will be held in the context instead of getting
output. With CFB and OFB modes and with stream ciphers,
there’s no padding, so the size of the resulting
ciphertext will be equal to the size of the plaintext.
If using a block-based mode, EVP_DecryptFinal
first checks to see if the padding on the last block is in the right
format. If not, the function returns 0, signifying failure.
Otherwise, it flushes any remaining data to the buffer passed in as
the second argument, and writes the number of bytes flushed into the
third argument, a reference parameter. For other modes, this call
does nothing.
Example 6-5 shows a simple function that decrypts a
block of encrypted text into a dynamically allocated buffer and
returns it. This function can be used to incrementally decrypt, and
thus requires EVP_DecryptFinal to be called when a
block-based mode is used. Of course,
EVP_DecryptInit
should always be called before passing a
context to this function.
char *decrypt_example(EVP_CIPHER_CTX *ctx, char *ct, int inl)
{
/* We're going to null-terminate the plaintext under the assumption it's
* non-null terminated ASCII text. The null can be ignored otherwise.
*/
char *pt = (char *)malloc(inl + EVP_CIPHER_CTX_block_size(ctx) + 1);
int ol;
EVP_DecryptUpdate(ctx, pt, &ol, ct, inl);
if (!ol) /* there's no block to decrypt */
{
free(pt);
return NULL;
}
pt[ol] = 0;
return pt;
}As is the case with encryption, factoring in the cipher’s block length is strictly only necessary when using a block-based cipher mode.
Using the above function, and the ones we previously developed,
let’s look at Example 6-6, which
encrypts a 15-byte string using
Blowfish in CBC mode, then passes it into
decrypt_example in two parts. Note, we use the
macro EVP_MAX_BLOCK_LENGTH, which exists only in
0.9.7 and later. If you are using an earlier version of OpenSSL, you
can define this macro to the value
“64”, which is the largest possible
block size in 0.9.6c and earlier.
int main(int argc, char *argv[])
{
EVP_CIPHER_CTX ctx;
char key[EVP_MAX_KEY_LENGTH];
char iv[EVP_MAX_IV_LENGTH];
char *ct, *out;
char final[EVP_MAX_BLOCK_LENGTH];
char str[] = "123456789abcdef";
int i;
if (!seed_prng( ))
{
printf("Fatal Error! Unable to seed the PRNG!\n");
abort( );
}
select_random_key(key, EVP_MAX_KEY_LENGTH);
select_random_iv(iv, EVP_MAX_IV_LENGTH);
EVP_EncryptInit(&ctx, EVP_bf_cbc( ), key, iv);
ct = encrypt_example(&ctx, str, strlen(str), &i);
printf("Ciphertext is %d bytes.\n", i);
EVP_DecryptInit(&ctx, EVP_bf_cbc( ), key, iv);
out = decrypt_example(&ctx, ct, 8);
printf("Decrypted: >>%s<<\n", out);
out = decrypt_example(&ctx, ct + 8, 8);
printf("Decrypted: >>%s<<\n", out);
if (!EVP_DecryptFinal(&ctx, final, &i))
{
printf("Padding incorrect.\n");
abort( );
}
final[i] = 0;
printf("Decrypted: >>%s<<\n", final);
}If we run this example, the first time we try to output decrypted
plaintext, we will see nothing, even though we fed the decryption
routine a full block of data. Note that when we feed the remainder of
the data, we are passing in eight bytes, because the encryption
routine padded the data to a block-aligned length. At that point, one
block will be output, and the second block will be held in reserve
until there is more data, or until
EVP_DecryptFinal is called.
If we were to change the cipher to RC4, the above example would
compile, but give slightly incorrect output. The length of the
encrypted text would be 15 bytes, not 16, due to the lack of padding.
As a result, passing in 16 bytes of ciphertext to the decrypt routine
will cause a block of garbage to be decrypted. Changing the third
argument in the second call to decrypt_example to
seven fixes the problem.
It is almost never desirable to use ECB mode for encryption. Schneier recommends it for encrypting other keys or other situations in which the data is short and random. However, this advice applies only when the key length is no larger than the cipher block length. Additionally, if you wish to include an integrity check alongside your data (which is almost always a good idea), ECB again becomes undesirable.
Another occasion when people think to use ECB is when encrypting datagrams to be sent over a UDP connection. The problem is that packets may show up out of order, or not at all. All basic cipher modes besides ECB require an ordered, reliable stream of data.
CBC mode is much better suited to handling UDP traffic. A single key can be used to encrypt all data, but each packet gets initialized with a randomly chosen IV, which can be sent alongside the encrypted data.
Counter mode is slightly better suited than CBC for encrypting UDP traffic. One advantage of counter mode over OFB and other modes that simulate stream ciphers is that it can inherently survive data loss—the current state of the counter can be passed in the clear each time a packet is sent. Another major advantage of counter mode is that it allows for parallelization, which is not supported by any of the default modes, except for ECB (a CBC-based approach could parallelize at the packet level, but could not parallelize the processing of data within a single packet). Another feature unique to OFB is that most of the work can be done offline. That is, you can generate a keystream before there is even data available to encrypt, while you have spare CPU cycles.
Additionally, because counter mode essentially supports arbitrarily jumping around a data stream, it can enable file encryption where random access to the data is still possible. Moreover, in theory, counter mode should be able to handle the UDP encryption problem without a rekey for every packet. However, current limitations of the OpenSSL library make that goal difficult to achieve, although the forthcoming 0.9.7 release will fix the problem.
OpenSSL currently doesn’t support counter mode, but it is simple to implement yourself. Counter mode effectively turns a block cipher into a stream cipher. For each block of plaintext, we encrypt a counter of exactly one block in length, which gets XOR’d with the corresponding block of plaintext. Then, the counter is incremented in some way. Increasing the counter by one or using a PRNG to increment the counter both work. The PRNG doesn’t even need to be cryptographically secure, though it must not repeat items before enumerating all possible values.
We can prevent the attacker from seeing the counter if need be. Let’s say that, in addition to an agreed-upon key, we share a second secret of the same length. Additionally, a sequence number is sent in the clear with each packet. To generate the counter used to encrypt the first data block in that packet, we concatenate the second shared secret with the sequence number, and cryptographically hash that. We show how to use cryptographic hashes in Chapter 7.
Counter mode is easily implemented by keeping track of a counter that is encrypted in ECB mode once for each block. In our UDP example, each packet would have several blocks. Only one counter should need to be sent per packet, because the receiving end should be able to recreate the counter values for the subsequent blocks.
Example 6-7 shows an implementation of counter mode
that will work for any cipher in ECB mode. There is just one
function,
counter_encrypt_or_decrypt
, as encryption and decryption are identical
in counter mode.
int counter_encrypt_or_decrypt(EVP_CIPHER_CTX *ctx, char *pt, char *ct, int len,
unsigned char *counter);ctx
The cipher context to use.
pt
A buffer containing the data to be encrypted or decrypted.
ct
A buffer that will contain the encrypted or decrypted data.
len
The number of bytes from the input buffer, pt, to
process.
counter
The counter.
In this example, the counter must be the same size as the block size of the cipher we’re using (we query the cipher’s block size to specify that value). Cipher block sizes are usually bigger than 32 bits, so it is best to represent the counter as an array of unsigned bytes. This function also modifies the counter in place as it processes blocks. To increment the counter, we simply increment the leftmost byte until it rolls over, at which point we increment the byte next to it, and so on. Note that it’s extremely important not to reuse counter values with a single key, not even across a reboot. If there is any chance of reusing a counter, be sure to change the key.
The next function will return -1 if it determines that the cipher is
not in ECB mode (we need to use this mode in order to implement
counter mode; do not take this example as an endorsement of ECB in
general). This is accomplished by calling
EVP_CIPHER_CTX_mode
, which returns a number. If that number is
equal to the constant EVP_CIPH_ECB_MODE, then we
know the cipher was initialized improperly. See the documentation on
the book’s web site for a list of other valid mode
constants.
Note that the code in Example 6-7 should not be used without also using a MAC to provide data integrity, as discussed in Chapter 7.
int counter_encrypt_or_decrypt(EVP_CIPHER_CTX *ctx, char *pt, char *ct, int len,
unsigned char *counter)
{
int i, j, where = 0, num, bl = EVP_CIPHER_CTX_block_size(ctx);
char encr_ctrs[len + bl]; /* Encrypted counters. */
if (EVP_CIPHER_CTX_mode(ctx) != EVP_CIPH_ECB_MODE)
return -1;
/* <= is correct, so that we handle any possible non-aligned data. */
for (i = 0; i <= len / bl; i++)
{
/* Encrypt the current counter. */
EVP_EncryptUpdate(ctx, &encr_ctrs[where], &num, counter, bl);
where += num;
/* Increment the counter. Remember it's an array of single characters */
for (j = 0; j < bl / sizeof(char); j++)
{
if (++counter[j])
break;
}
}
/* XOR the key stream with the first buffer, placing the results in the
* second buffer.
*/
for (i = 0; i < len; i++)
ct[i] = pt[i] ^ encr_ctrs[i];
return 1; /* Success. */
}As we discussed, the above example requires the state of the counter
to be kept externally. Another option is to make a
COUNTER_CTX data type that could hold a pointer to
the underlying cipher context and the current state of the counter.
However, this less-abstract API makes it easier to use in a situation
in which the counter may need to be reset explicitly after
desynchronization, such as when dealing with UDP traffic.
So far, we have looked at how to use the EVP API to perform encryption and decryption. While we’ve examined some basic examples, we haven’t looked at real-world examples. The primary reason is that we don’t recommend using symmetric key encryption without a MAC; in cases where an attacker has read access to data, you should be worried about her also gaining write access. Therefore, we give real-world examples of using encryption along with MACs in Chapter 7.
When you do use MACs, use them with independent keys (that is, do not MAC with your encryption keys) and use them to validate all of the data, including anything sent in the clear. In particular, when using counter mode, make sure to include the counter value in the data that you include in the MAC calculation.
Extending this recommendation, whenever you design protocols based on encryption, avoid any communication in plaintext at all, and MAC anything that does need to be plaintext. In particular, if you do plaintext protocol negotiation before a key exchange, you should MAC the payloads of each message in the negotiation, so that after a key is agreed upon, both sides can validate the negotiation. For example, let’s say that a client connects to the server and immediately asks to speak Version 2 of protocol X, and receives a response saying the server speaks only the insecure Version 1. If it turns out that a man in the middle told the server the client wanted Version 1, and fakes the response from the server, then neither the client nor the server would notice, and would wind up speaking an insecure version of the protocol.
Another recommendation is to design your protocol to be fault-tolerant. In particular, when using MACs to validate messages, be prepared to perform reasonable error handling if the data doesn’t authenticate on the receiving end. If your protocol fails in such a situation, denial of service attacks will be quite easy.
Finally, be sure to protect yourself against dictionary and capture-replay type attacks. One thing you can do is add sequence numbers to the beginning of each message. It’s also a good idea to place unique information per-user or per-connection near the beginning of each message.
[1] This happens only if padding is turned on, of course.
[2] For the curious, here is how standard (PKCS) padding works. If n bytes of padding are needed, then n is used as the value of each byte. For example, the value of a one-byte pad (expressed in hexadecimal) is 0x01, and the value of an eight-byte pad is 0x0808080808080808. This way, the extent of the pad can be calculated unambiguously by looking at the last byte of the padded plaintext.
[3] Actually, the current implementation limits the output to six bytes longer than the input. However, you should not count on that behavior.
[4] Strictly speaking, this isn’t entirely true in CFB mode because the first block can be buffered.