[ Team LiB ] |
5.16 Using a High-Level, Error-Resistant Encryption and Decryption API5.16.1 ProblemYou 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 SolutionUse 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
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:
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:
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.
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 AlsoRecipe 4.11, Recipe 5.5, Recipe 5.10, Recipe 9.12, Recipe 11.2 |
[ Team LiB ] |