5.16 Using a High-Level, Error-Resistant Encryption and Decryption API

5.16.1 Problem

You want to do encryption or decryption without the hassle of worrying about choosing an encryption algorithm, performing an integrity check, managing a nonce, and so on.

5.16.2 Solution

Use the following "Encryption Queue" implementation, which relies on the reference CWC mode implementation (discussed in Recipe 5.10) and the key derivation function from Recipe 4.11.

5.16.3 Discussion

Be sure to take into account the fact that functions in this API can fail, particularly the decryption functions. If a decryption function fails, you need to fail gracefully. In Recipe 9.12, we discuss many issues that help ensure robust network communication that we don't cover here.

This recipe provides an easy-to-use interface to symmetric encryption. The two ends of communication must set up cipher queues in exactly the same configuration. Thereafter, they can exchange messages easily until the queues are destroyed.

This code relies on the reference CWC implementation discussed in Recipe 5.10. We use CWC mode because it gives us both encryption and integrity checking using a single key with a minimum of fuss.

We add a new data type, SPC_CIPHERQ, which is responsible for keeping track of queue state. Here's the declaration of the SPC_CIPHERQ data type:

typedef struct {   cwc_t         ctx;   unsigned char nonce[SPC_BLOCK_SZ]; } SPC_CIPHERQ;

SPC_CIPHERQ objects are initialized by calling spc_cipherq_setup( ), which requires the code from Recipe 5.5, as well as an implementation of the randomness API discussed in Recipe 11.2:

#include <stdlib.h> #include <string.h> #include <cwc.h>     #define MAX_KEY_LEN (32)  /* 256 bits */     size_t spc_cipherq_setup(SPC_CIPHERQ *q, unsigned char *basekey, size_t keylen,                           size_t keyuses) {   unsigned char dk[MAX_KEY_LEN];   unsigned char salt[5];       spc_rand(salt, 5);   spc_make_derived_key(basekey, keylen, salt, 5, 1, dk, keylen);   if (!cwc_init(&(q->ctx), dk, keylen * 8)) return 0;   memcpy(q->nonce, salt, 5);   spc_memset(basekey, 0, keylen);   return keyuses + 1; }

The function has the following arguments:

q

SPC_CIPHERQ context object.

basekey

Shared key used by both ends of communication (the "base key" that will be used to derive session keys).

keylen

Length of the shared key in bytes, which must be 16, 24, or 32.

keyuses

Indicates how many times the current key has been used to initialize a SPC_CIPHERQ object. If you are going to reuse keys, it is important that this argument be used properly.

On error, spc_cipherq_setup() returns 0. Otherwise, it returns the next value it would expect to receive for the keyuses argument. Be sure to save this value if you ever plan to reuse keys.

Note also that basekey is erased upon successful initialization.

Every time you initialize an SPC_CIPHERQ object, a key specifically for use with that queue instance is generated, using the basekey and the keyuses arguments. To derive the key, we use the key derivation function discussed in Recipe 4.11. Note that this is useful when two parties share a long-term key that they wish to keep reusing. However, if you exchange a session key at connection establishment (i.e., using one of the techniques from Chapter 8), the key derivation step is unnecessary, because reusing {key, nonce} pairs is already incredibly unlikely in such a situation.

Both communicating parties must initialize their queue with identical parameters.

When you're done with a queue, you should deallocate internally allocated memory by calling spc_cipherq_cleanup( ):

void spc_cipherq_cleanup(SPC_CIPHERQ *q) {   spc_memset(q, 0, sizeof(SPC_CIPHERQ)); }

Here are implementations of the encryption and decryption operations (including a helper function), both of which return a newly allocated buffer containing the results of the appropriate operation:

static void increment_counter(SPC_CIPHERQ *q) {   if (!++q->nonce[10]) if (!++q->nonce[9]) if (!++q->nonce[8]) if (!++q->nonce[7])     if (!++q->nonce[6]) ++q->nonce[5];  }     unsigned char *spc_cipherq_encrypt(SPC_CIPHERQ *q, unsigned char *m, size_t mlen,                                     size_t *ol) {   unsigned char *ret;       if (!(ret = (unsigned char *)malloc(mlen + 16))) {     if (ol) *ol = 0;     return 0;   }   cwc_encrypt(&(q->ctx), 0, 0, m, mlen, q->nonce, ret);   increment_counter(q);   if (ol) *ol = mlen + 16;   return ret; }   unsigned char *spc_cipherq_decrypt(SPC_CIPHERQ *q, unsigned char *m, size_t mlen,                                     size_t *ol) {   unsigned char *ret;       if (!(ret = (unsigned char *)malloc(mlen - 16))) {     if (ol) *ol = 0;     return 0;   }   if (!cwc_decrypt(&(q->ctx), 0, 0, m, mlen, q->nonce, ret)) {     free(ret);     if (ol) *ol = 0;     return 0;   }   increment_counter(q);   if (ol) *ol = mlen - 16;   return ret; }

The functions spc_cipherq_encrypt( ) and spc_cipherq_decrypt( ) each take four arguments:

q

SPC_CIPHERQ object to use for encryption or decryption.

m

Message to be encrypted or decrypted.

mlen

Length of the message to be encrypted or decrypted, in bytes.

ol

The number of bytes returned from the encryption or decryption operation is stored in this integer pointer. This may be NULL if you don't need the information. The number of bytes returned will always be the message length plus 16 bytes for encryption, or the message length minus 16 bytes for decryption.

These functions don't check for counter rollover because you can use this API to send over 250 trillion messages with a single key, which should be adequate for any use.

Instead of using such a large counter, it is a good idea to use only five bytes for the counter and initialize the rest with a random salt value. The random salt helps prevent against a class of problems in which the attacker amortizes the cost of an attack by targeting a large number of possible keys at once. In Recipe 9.12, we show a similar construction that uses both a salt and a counter in the nonce.

If you do think you might send more messages under a single key, be sure to rekey in time. (This scheme is set up to handle at least four trillion keyings with a single base key.)

In the previous code, the nonces are separately managed by both parties in the communication. They each increment by one when appropriate, and will fail to decrypt a message with the wrong nonce. Thus, this solution prevents capture replay attacks and detects message drops or message reordering, all as a result of implicit message numbering. Some people like explicit message numbering and would send at least a message number, if not the entire nonce, with each message (though you should always compare against the previous nonce to make sure it's increasing). In addition, if there's a random portion to the nonce as we suggested above, the random portion needs to be communicated to both parties. In Recipe 9.12, we send the nonce explicitly with each message, which helps communicate the portion randomly selected at connection setup time.

It's possible to mix and match calls to spc_cipherq_encrypt( ) and spc_cipherq_decrypt( ) using a single context. However, if you want to use this API in this manner, do so only if the communicating parties send messages in lockstep. If parties can communicate asynchronously (that is, without taking turns), there is the possibility for a race condition in which the SPC_CIPHERQ states on each side of the communication get out of sync, which will needlessly cause decryption operations to fail.

If you need to perform asynchronous communication with an infrastructure like this, you could use two SPC_CIPHERQ instances, one where the client encrypts messages for the server to decrypt, and another where the server encrypts messages for the client to decrypt.

The choice you need to make is whether each SPC_CIPHERQ object should be keyed separately or should share the same key. Sharing the same key is possible, as long as you ensure that the same {key, nonce} pair is never reused. The way to do this is to manage two sets of nonces that can never collide. Generally, you do this by setting the high bit of the nonce buffer to 1 in one context and 0 in another context.

Here's a function that takes an existing context that has been set up, but not otherwise used, and turns it into two contexts with the same key:

void spc_cipherq_async_setup(SPC_CIPHERQ *q1, SPC_CIPHERQ *q2) {   memcpy(q2, q1, sizeof(SPC_CIPHERQ));   q1->nonce[0] &= 0x7f;  /* The upper bit of q1's nonce is always 0. */   q2->nonce[0] |= 0x80;  /* The upper bit of q2's nonce is always 1. */ }

We show a similar trick in which we use only one abstraction in Recipe 9.12.

5.16.4 See Also

Recipe 4.11, Recipe 5.5, Recipe 5.10, Recipe 9.12, Recipe 11.2



Secure Programming Cookbook for C and C++
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2005
Pages: 266

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net