6.20 Encrypting with a Hash Function

6.20.1 Problem

You want to encrypt with a hash function, possibly because you want only a single cryptographic primitive and to use a hash function instead of a block cipher.

6.20.2 Solution

Use a hash-based MAC in counter mode.

6.20.3 Discussion

Use a separate key from the one you use to authenticate, and don't forget to use the MAC for message authentication as well!

You can turn any MAC into a stream cipher essentially by using the MAC in counter (CTR) mode. You should not use a hash function by itself, because it's difficult to ensure that you're doing so securely. Basically, if you have a MAC built on a hash function that is known to be a secure MAC, it will be secure for encryption in CTR mode.

There is no point in using any MAC that uses a block cipher in any way, such as OMAC, CMAC, or MAC127 (see Recipe 6.4 for a discussion of MAC solutions). Instead, just use the underlying block cipher in CTR mode, which will produce the same results. This recipe should be used only when you don't want to use a block cipher.

Using a MAC in CTR mode is easy. As illustrated in Figure 6-7, key it, then use it to "MAC" a nonce concatenated with a counter. XOR the results with the plaintext.

Figure 6-7. Encrypting with a MAC in counter mode

For example, here's a function that encrypts a stream of data using the HMAC-SHA1 implementation from Recipe 6.10:

#include <stdlib.h>
#include <string.h>

#define NONCE_LEN  16
#define CTR_LEN    16
#define MAC_OUT_SZ 20
unsigned char *spc_MAC_encrypt(unsigned char *in, size_t len, unsigned char *key,
                                 int keylen, unsigned char *nonce) {
  /* We're using a 128-bit nonce and a 128-bit counter, packed into one variable */
  int           i;
  size_t        blks;
  SPC_HMAC_CTX  ctx;
  unsigned char ctr[NONCE_LEN + CTR_LEN];
  unsigned char keystream[MAC_OUT_SZ];
  unsigned char *out;
  if (!(out = (unsigned char *)malloc(len))) abort(  );
  SPC_HMAC_Init(&ctx, key, keylen);
  memcpy(ctr, nonce, NONCE_LEN);
  memset(ctr + NONCE_LEN, 0, CTR_LEN);
  blks = len / MAC_OUT_SZ;
  while (blks--) {
    SPC_HMAC_Update(&ctx, ctr, sizeof(ctr));
    SPC_HMAC_Final(out, &ctx);
    i = NONCE_LEN + CTR_LEN;
    /* Increment the counter. */
    while (i-- != NONCE_LEN)
      if (++ctr[i]) break;
    for (i = 0;  i < MAC_OUT_SZ;  i++) *out++ = *in++ ^ keystream[i];
  if (len % MAC_OUT_SZ) {
    SPC_HMAC_Update(&ctx, ctr, sizeof(ctr));
    SPC_HMAC_Final(out, &ctx);
    for (i = 0;  i < len % MAC_OUT_SZ;  i++) *out++ = *in++ ^ keystream[i];
  return out;

Note that this code is not optimized; it works on individual characters to avoid potential endian-ness problems.

6.20.4 See Also

Recipe 6.4, Recipe 6.10