You 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.
OFB 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.
You should probably use a higher-level abstraction, such as the one discussed in Recipe 5.16. Use a raw mode only when absolutely necessary, because there is a huge potential for introducing a security vulnerability by accident. If you still want to use OFB, be sure to use a message authentication code with it.
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.
This 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.
This 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.
These two functions also erase the key from memory before exiting. You may want to have them erase the plaintext as well.
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.
Let'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:
ctx
Pointer to the SPC_OFB_CTX
object associated with
the current message.
in
Pointer to a buffer containing the data to be encrypted or decrypted.
il
Number of bytes contained in the input buffer.
out
Pointer to the output buffer, which needs to be exactly as long as the input buffer.
Our implementation of this function always returns 1, but a hardware-based implementation might have an unexpected failure, so it's important to check the return value!
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.
Because this API is developed with PKCS #11 in mind,
it's somewhat more low-level than it needs to be,
and therefore is a bit difficult to use properly. First, you need to
be sure the output buffer is big enough to hold the input; otherwise,
you will have a buffer overflow. Second, you need to make sure the
out
argument always points to the first unused
byte in the output buffer. Otherwise, you will keep overwriting the
same data every time spc_ofb_update( )
outputs.
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; }
[12] As with CFB mode, the "feedback size" could conceivably be smaller than the block size, but such schemes aren't secure.