[ Team LiB ] |
5.8 Using a Generic OFB Mode Implementation5.8.1 ProblemYou want a more high-level interface for OFB mode than your library provides. Alternatively, you want a portable OFB interface, or you have only a block cipher implementation and you would like to use OFB mode. 5.8.2 SolutionOFB mode encrypts by generating keystream, then combining the keystream with the plaintext via XOR. OFB generates keystream one block at a time. Each block of keystream is produced by encrypting the previous block of keystream, except for the first block, which is generated by encrypting the nonce. Many libraries provide an OFB implementation. If you need code implementing this mode, you will find it in the following Section 5.8.3. 5.8.3 Discussion
OFB mode is a stream-based mode. Encryption occurs by XOR'ing the keystream bytes with the plaintext bytes, as shown in Figure 5-3. The keystream is generated one block at a time, by encrypting the previous keystream block.[12] The first block is generated by encrypting the nonce.
Figure 5-3. OFB modeThis mode shares many properties with counter mode (CTR), but CTR mode has additional benefits. OFB mode is therefore seeing less and less use these days. Of course, we recommend a higher-level mode than both of these modes, one that provides stronger security guarantees—for example, CWC or CCM mode. In Recipe 5.4, we discuss the advantages and drawbacks of OFB and compare it to other popular modes. Many libraries already come with an implementation of OFB mode for any ciphers they support. However, some don't. For example, you may only get an implementation of the raw block cipher when you obtain reference code for a new cipher. In the following sections we present a reasonably optimized implementation of OFB mode that builds upon the raw block cipher interface presented in Recipe 5.5. It also requires the spc_memset( ) function from Recipe 13.2. 5.8.3.1 The high-level APIThis implementation has two APIs. The first is a high-level API, which takes a message as input and returns a dynamically allocated result. unsigned char *spc_ofb_encrypt(unsigned char *key, size_t kl, unsigned char *nonce, unsigned char *in, size_t il); unsigned char *spc_ofb_decrypt(unsigned char *key, size_t kl, unsigned char *nonce, unsigned char *in, size_t il) Both of these functions output the same number of bytes as were input, unless a memory allocation error occurs, in which case 0 is returned. The decryption routine is exactly the same as the encryption routine and is implemented by macro.
Here's the implementation of the interface: #include <stdlib.h> #include <string.h> unsigned char *spc_ofb_encrypt(unsigned char *key, size_t kl, unsigned char *nonce, unsigned char *in, size_t il) { SPC_OFB_CTX ctx; unsigned char *out; if (!(out = (unsigned char *)malloc(il))) return 0; spc_ofb_init(&ctx, key, kl, nonce); spc_ofb_update(&ctx, in, il, out); spc_ofb_final(&ctx); return out; } #define spc_ofb_decrypt spc_ofb_encrypt Note that the previous code depends on the SPC_OFB_CTX data type and the incremental OFB interface, both discussed in the following sections. 5.8.3.2 The incremental APILet's look at the SPC_OFB_CTX data type. It's defined as: typedef struct { SPC_KEY_SCHED ks; int ix; unsigned char nonce[SPC_BLOCK_SZ]; } SPC_OFB_CTX; The ks field is an expanded version of the cipher key (block ciphers generally use a single key to derive multiple keys for internal use). The ix field is used to determine how much of the last block of keystream we have buffered (i.e., that hasn't been used yet). The nonce field is really the buffer in which we store the current block of the keystream. To begin encrypting or decrypting, we need to initialize the mode. Initialization is the same operation for both encryption and decryption: void spc_ofb_init(SPC_OFB_CTX *ctx, unsigned char *key, size_t kl, unsigned char *nonce) { SPC_ENCRYPT_INIT(&(ctx->ks), key, kl); spc_memset(key,0, kl); memcpy(ctx->nonce, nonce, SPC_BLOCK_SZ); ctx->ix = 0; }
Never use the same nonce (often called an IV in this context) twice with a single key. Use a secure random value or a counter. See Recipe 4.9 for more information on nonces. Now we can add data as we get it using the spc_ofb_update( ) function. This function is particularly useful when a message arrives in pieces. You'll get the same results as if it all arrived at once. When you want to finish encrypting or decrypting, call spc_ofb_final( ).
The function spc_ofb_update( ) has the following signature: int spc_ofb_update(OFB_CTX *ctx, unsigned char *in, size_t il, unsigned char *out); This function has the following arguments:
This API is in the spirit of PKCS #11, which provides a standard cryptographic interface to hardware. We do this so that the above functions can have the bulk of their implementations replaced with calls to PKCS #11-compliant hardware. PKCS #11 APIs generally pass out data explicitly indicating the length of data outputted, while we ignore that because it will always be zero on failure or the size of the input buffer on success. Also note that PKCS #11-based calls tend to order their arguments differently from the way we do, and they will not generally wipe key material, as we do in our initialization and finalization routines.
Here's our implementation of spc_ofb_update( ): int spc_ofb_update(SPC_OFB_CTX *ctx, unsigned char *in, size_t il, unsigned char *out) { int i; if (ctx->ix) { while (ctx->ix) { if (!il--) return 1; *out++ = *in++ ^ ctx->nonce[ctx->ix++]; ctx->ix %= SPC_BLOCK_SZ; } } if (!il) return 1; while (il >= SPC_BLOCK_SZ) { SPC_DO_ENCRYPT(&(ctx->ks), ctx->nonce, ctx->nonce); for (i = 0; i < SPC_BLOCK_SZ / sizeof(int); i++) ((int *)out)[i] = ((int *)in)[i] ^ ((int *)ctx->nonce)[i]; il -= SPC_BLOCK_SZ; in += SPC_BLOCK_SZ; out += SPC_BLOCK_SZ; } SPC_DO_ENCRYPT(&(ctx->ks), ctx->nonce, ctx->nonce); for (i = 0; i < il; i++) *out++ = *in++ ^ ctx->nonce[ctx->ix++]; return 1; } To finalize either encryption or decryption, use the spc_ofb_final( ) call, which never needs to output anything, because OFB is a streaming mode: int spc_ofb_final(SPC_OFB_CTX *ctx) { spc_memset(&ctx, 0, sizeof(SPC_OFB_CTX)); return 1; } 5.8.4 See AlsoRecipe 4.9, Recipe 5.4, Recipe 5.5, Recipe 5.16, Recipe 13.2 |
[ Team LiB ] |