Chapter 7. Hashes and MACs

In the previous chapter, we looked at the most fundamental part of OpenSSL’s cryptography library, symmetric ciphers. In this chapter, we look at the API for cryptographic hashing algorithms, also commonly called message digest algorithms or cryptographic one-way hash functions. Additionally, we will examine OpenSSL’s interface to message authentication codes (MACs), also known as keyed hashes.

Overview of Hashes and MACs

We introduced the basic concepts behind cryptographic hashes and MACs in Chapter 1. Here, we describe the fundamental properties of these cryptographic primitives that you should understand before integrating them into your applications. As mentioned in Chapter 6, we provide only the minimum background information that you need to understand as a developer. If you need more background, or would like to see under the hood of any of the algorithms we discuss, refer to a general-purpose cryptography reference, such as Bruce Schneier’s Applied Cryptography.

Cryptographic one-way hashes take arbitrary binary data as an input and produce a fixed-size binary string as an output, called the hash value or the message digest. Passing the same message through a single hash function always yields the same result. There are several important properties exhibited by cryptographic message digests. First, the digest value should contain no information that could be used to determine the original input. For that to be true, a one-bit change in the input data should change many bits in the digest value (on average, half). Second, it should be extremely difficult to construct a second message that yields the same resulting hash value. Third, it should also be difficult to find any two messages that yield the same hash value.

The most conservative characterization of the security afforded by a given hash function is measured by how hard it is to find two arbitrary messages that yield the same hash value. Generally, the security of a well-respected hash function that has a digest size of n bits should be about as secure as a well-respected symmetric cipher with half as many bits. For example, SHA1, which has a 160-bit digest size, is about as resistant to attack as RC5 with 80-bit keys. Some uses of these algorithms give security equal to their bit length that’s just a good, conservative metric.

People frequently use cryptographic hash functions that they believe provide security, but that don’t really provide very much. For example, it is common to release software along with an MD5 digest of the software package (MD5 is a common cryptographic hash function). The intention is to use the digest as a checksum. The person downloading software should also obtain the MD5 digest, and then calculate the digest himself on the downloaded software. If the two digests match, it would indicate that the downloaded software is unaltered.

Unfortunately, there are easy ways to attack this scheme. Suppose an attacker has maliciously modified a copy of the distribution of software package X, resulting in package Y. If the attacker can break onto the server and replace X with Y, then certainly, the checksum MD5(X) is also easily replaceable with the checksum MD5(Y). When a user validates the downloaded checksum, he will be none the wiser. Even without access to the actual server, attackers could replace X with Y and MD5(X) with MD5(Y) as they traverse the network.

The fundamental problem is that nothing in this scenario is secret. A much better solution for this kind of scenario is a digital signature, which anyone can verify, but only someone with the correct private key can generate (see Chapter 8).

Hash functions by themselves aren’t often good for security purposes. The major exception is password storage. In such a situation, passwords are not stored, only hashes of passwords are stored, usually combined with a known "salt” value to avoid dictionary attacks in cases where the password database is stolen. When a user tries to log in, the hash of the entered password is compared against the one stored in the password database. If it’s the correct password, the hashes will be identical.

Even this scenario works only if a trusted data source collects the authentication information through a trusted data path. If a client computes the hash and sends it in the clear over a network, an attacker can capture the hash and replay the information later to log in. Worse, if the server computes the hash, but the client sent the password in the clear over a network, an attacker could capture the transmission of the password.

One common use of hashes is as primitives in other cryptographic operations. For example, digital signature schemes generally work by hashing the input, then encrypting the hash with a private key. Doing so is generally far more efficient than performing public key encryption on a large input. Another frequent use is to remove any trace of patterns in data such as cryptographic keys. For example, you should hash your key material to make an RC4 key, instead of using the key material directly.

Another use of hashes is to ensure the message integrity of encrypted data, by encrypting the hash of a message along with the message itself. This is a primitive version of a message authentication code (MAC). A MAC generally uses a regular hash function as a primitive. The MAC algorithm produces a hash value from the data to protect a secret key. Only people with the correct secret key can forge the hash value, and only people with the secret key can authenticate the hash value.

One good thing about MACs is that they can provide integrity, even in the absence of encryption. Another good thing is that the best MACs tend to have provable security properties under reasonable assumptions about the strength of the hash algorithm in use. The algorithm we just described as an example doesn’t have either of these advantages.

Like other cryptographic primitives, you should avoid creating your own MAC algorithm, even if it seems easy. There are good algorithms with provable properties, such as HMAC, which is currently the only MAC provided by OpenSSL. Why take the risk?

Hashing with the EVP API

Much like with symmetric cryptography, OpenSSL’s cryptographic library has an API for each hash algorithm it provides, but the EVP API provides a single, simple interface to these algorithms. Just as with symmetric key encryption, there are three calls, one for initialization, one for “updating” (adding text to the context), and one for finalization, which yields the message digest.

At initialization time, you must specify the algorithm you wish to use. Currently, OpenSSL provides six different digest algorithms: MDC2, MD2, MD4, MD5, SHA1, and RIPEMD-160. The first four have digest sizes that are only 128 bits. We recommend that you avoid them except to support legacy applications. In addition, there are known attacks on MD4, and it is widely considered to be a broken algorithm. SHA1 is more common than RIPEMD-160 and is faster, but the latter is believed to have a slightly better security margin.

For each digest, at least one function returns an instance of the algorithm. Look up algorithms by name by calling OpenSSL_add_all_digests and EVP_get_digestbyname , and passing in an appropriate identifier. In both cases, a data structure of type EVP_MD represents the algorithm. Table 7-1 shows all of the message digest algorithms supported by OpenSSL, including the EVP call to get a reference to the algorithm, the digest name for lookup purposes, and the size of the resulting digests.

Table 7-1. Message digests and the EVP interface

Hash algorithm

EVP call for getting EVP_MD

String for lookup

Digest length (in bits)

MD2

EVP_md2( )

md2

128

MD4

EVP_md4( )

md4

128

MD5

EVP_md5( )

md5

128

MDC2

EVP_mdc2( )

mdc2

128

SHA1

EVP_sha1( )

EVP_dss1( )

sha1

dss1

160

RIPEMD-160

EVP_ripemd160( )

ripemd

160

The MDC2 algorithm is a construction for turning a block cipher into a hash function. It is usually used only with DES, and OpenSSL hardcodes this binding. The SHA1 and DSS1 algorithms are essentially the same; the only difference is that in a digital signature, SHA1 is used with RSA keys and DSS1 is used with DSA keys.

The EVP_DigestInit function initializes a context object, and it must be called before a hash can be computed.

void EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type);
ctx

The context object to be initialized.

type

The context for the message digest algorithm to use. This value is often obtained using one of the EVP calls listed in Table 7-1.

The OpenSSL “engine” package and the forthcoming Version 0.9.7 have a preferred version of this call named EVP_DigestInit_ex , which adds a third argument that is a pointer to an engine object. Passing in NULL will get you the default software implementation. Its return value is also different; it is an integer indicating success (nonzero) or failure (zero). Be sure to check the return value from the function, because it can fail.

The EVP_DigestUpdate function is used to include data in the computation of the hash. It may be called repeatedly to pass more data than will fit in a single buffer. For example, if you’re computing the hash of a large amount of data, it’s reasonable to break the data into smaller bytes so that you needn’t load an entire file into memory.

void EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *buf, unsigned int len);
ctx

The context object that is being used to compute a hash.

buf

A buffer containing the data to be included in the computation of the hash.

len

The number of bytes contained in the buffer.

Once all data to be considered for the hash has been passed to EVP_DigestUpdate, the resulting hash value can be retrieved using EVP_DigestFinal.

void EVP_DigestFinal(EVP_MD_CTX *ctx, unsigned char *hash, unsigned int *len);
ctx

The context object that is being used to compute a hash.

hash

A buffer into which the hash value will be placed. This buffer should always be at least EVP_MAX_MD_SIZE bytes in size.

len

A pointer to an integer that will receive the number of bytes placed into the hash value buffer. This argument may be specified as NULL if you don’t want or need to know this value.

Be sure to use EVP_DigestFinal_ex with EVP_DigestInit_ex, even though the arguments are no different. Once you’ve called EVP_DigestFinal or EVP_DigestFinal_ex, the context that you were using is no longer valid and must be re-initialized using EVP_DigestInit or EVP_DigestInit_ex before it can be used again. Also, be aware that the EVP_DigestFinal_ex function can fail.

Example 7-1 shows a function that performs message digests as an all-in-one operation. You pass in the name of an algorithm to use, a buffer of data to hash, an unsigned integer that denotes how much data to take from the buffer, and a pointer to an integer. The integer pointed to by the final argument gets the length of the resulting digest placed in it, and may be NULL if you’re not interested in its value. The digest value is allocated internal to the function and is returned as a result. If there is any sort of error, such as the specified algorithm not being found, the function returns NULL.

Example 7-1. Computing a hash value using the EVP API
unsigned char *simple_digest(char *alg, char *buf, unsigned int len, int *olen)
{
    const EVP_MD  *m;
    EVP_MD_CTX    ctx;
    unsigned char *ret;

    OpenSSL_add_all_digests(  );
    if (!(m = EVP_get_digestbyname(alg)))
        return NULL;
    if (!(ret = (unsigned char *)malloc(EVP_MAX_MD_SIZE)))
        return NULL;
    EVP_DigestInit(&ctx, m);
    EVP_DigestUpdate(&ctx, buf, len);
    EVP_DigestFinal(&ctx, ret, olen);
    return ret;
}

Message digests cannot be printed directly because they are binary data. Traditionally, when there’s a need to print a message digest, it is printed in hexadecimal. Example 7-2 shows a function that uses printf to print an arbitrary binary string in hexadecimal one byte at a time. It takes two parameters, the string, and an integer specifying the length of the string.

Example 7-2. Printing the hexadecimal representation of a hash value
void print_hex(unsigned char *bs, unsigned int n)
{
    int i;

    for (i = 0;  i < n;  i++)
        printf("%02x", bs[i]);
}

The code in Example 7-3 implements a simple sha1 command that is similar to the md5 command found on many systems. It gives SHA1 digests of files passed in on the command line. If the command is called with no arguments, then the standard input is hashed. Note that you can get the same results by running the command openssl sha1 (see Chapter 2).

Example 7-3. Computing SHA1 hashes of files
#define READSIZE 1024

/* Returns 0 on error, file contents on success */
unsigned char *read_file(FILE *f, int *len)
{
    unsigned char *buf = NULL, *last = NULL;
    unsigned char inbuf[READSIZE];
    int tot, n;

    tot = 0;
    for (;;)
    {
        n = fread(inbuf, sizeof(unsigned char), READSIZE, f);
        if (n > 0)
        {
            last = buf;
            buf = (unsigned char *)malloc(tot + n);
            memcpy(buf, last, tot);
            memcpy(&buf[tot], inbuf, n);
            if (last)
                free(last);
            tot += n;
            if (feof(f) > 0)
            {
                *len = tot;
                return buf;
            }
        }
        else
        {
            if (buf)
                free(buf);
            break;
        }
    }
    return NULL;
}

/* Returns NULL on error, the digest on success */
unsigned char *process_file(FILE *f, insigned int *olen)
{
    int           filelen;
    unsigned char *ret, *contents = read_file(f, &filelen);

    if (!contents)
        return NULL;
    ret = simple_digest("sha1", contents, filelen, olen);
    free(contents);
    return ret;
}

/* Return 0 on failure, 1 on success */
int process_stdin(void)
{
    unsigned int  olen;
    unsigned char *digest = process_file(stdin, &olen);

    if (!digest)
        return 0;
    print_hex(digest, olen);
    printf("\n");
    return 1;
}

/* Returns 0 on failure, 1 on success */
int process_file_by_name(char *fname)
{
    FILE          *f = fopen(fname, "rb");
    unsigned int  olen;
    unsigned char *digest;

    if (!f)
    {
        perror(fname);
        return 0;
    }
    digest = process_file(f, &olen);
    if (!digest)
    {
        perror(fname);
        fclose(f);
        return 0;
    }
    fclose(f);
    printf("SHA1(%s)= ", fname);
    print_hex(digest, olen);
    printf("\n");
    return 1;
}

int main(int argc, char *argv[])
{
    int i;

    if (argc == 1)
    {
        if (!process_stdin(  ))
            perror("stdin");
    }
    else
    {
        for (i = 1;  i < argc;  i++)
            process_file_by_name(argv[i]);
    }
}

Using MACs

The OpenSSL library provides only one MAC implementation, HMAC. For this reason, there’s no EVP interface to MACs. If all of the data to be MAC’d is available in memory at once (i.e., if you do not need to compute the MAC incrementally), then there is a single call named HMAC (include the header openssl/hmac.h) that takes care of everything.

unsigned char *HMAC(const EVP_MD *type, const void *key, int keylen,
                    const unsigned char *data, int datalen,
                    unsigned char *hash, unsigned int *hashlen);
type

A message digest to use. See Table 7-1 for a list of digests and functions to obtain a suitable EVP_MD object.

key

A buffer that contains the key that will be used.

keylen

The number of bytes in the key buffer that should be used for the key.

data

A buffer containing the data that an HMAC will be computed for.

datalen

The number of bytes in the data buffer that should be used.

hash

A buffer that the computed message digest will be placed in. It should always be at least EVP_MAX_MD_SIZE bytes in length.

hashlen

A pointer to an integer that will receive the number of bytes of the hash buffer that were filled. This argument may be specified as NULL if you’re not interested in this information.

The return value from the HMAC call will be a pointer to the hash buffer. The output buffer, hash, may also be specified as NULL, but we strongly recommend against it. When no output buffer is specified, an internal global buffer will be used. Use of this buffer is not thread-safe.

The key used can be of any size. We recommend your key be as long as any key you’re using for a symmetric cipher (preferably 80 bits or more). However, we advise against using the same key for your MAC that you use for encryption. Instead, generate and exchange a second key.

Example 7-4 shows how to use the HMAC call to MAC files specified on the command line using a hardcoded key and the SHA1 digest algorithm. Of course, in a real application, you should be sure to choose a cryptographically strong key (see the function select_random_key from Chapter 6). Do not use the same key for encryption and MACing under any circumstances.

Example 7-4. Computing a MAC with the HMAC function
/* Warning: DO NOT USE THIS KEY.  Generate one randomly.  This is for
 * the sake of example only.
 */
static const char key[16] = { 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 
                              0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00 };

/* Returns 0 on failure, 1 on success */
int HMAC_file_and_print(unsigned char *fname)
{
    FILE          *f = fopen(fname, "rb");
    unsigned char *contents;
    unsigned char result[EVP_MAX_MD_SIZE];
    unsigned int  flen, dlen;

    if (!f)
        return 0;
    contents = read_file(f, &flen);
    fclose(f);
    if (!contents)
        return 0;

    HMAC(EVP_sha1(  ), key, sizeof(key), contents, flen, result, &dlen);

    printf("HMAC(%s, ", fname);
    print_hex(key, sizeof(key));
    printf(")= ");
    print_hex(result, dlen);
    printf("\n");

    return 1;
}

Validating MAC’d data is simple. Simply recompute the hash value and compare it against the transmitted hash value. If they are identical, then the message should not have been modified in transit. Example 7-5 shows a simple function that does a byte-for-byte comparison.

Example 7-5. A binary comparison function
/* Return 0 if equal, -1 if unequal */
int binary_cmp(unsigned char *s1, unsigned int len1,
               unsigned char *s2, unsigned int len2)
{
    int i, c, x;

    if (len1 != len2)
        return -1;

    c = len1 / sizeof(x);
    for (i = 0;  i < c;  i++)
    {
        if (*(unsigned long *)(s1 + (i * sizeof(x))) !=
            *(unsigned long *)(s2 + (i * sizeof(x))))
        {
            return -1;
        }
    }
    for (i = c * sizeof(x);  i < len1;  i++)
    {
        if (s1[i] != s2[i])
            return -1;
    }

    return 0;
}

If the data to be authenticated needs to be authenticated incrementally, the HMAC API provides a set of methods that works much the same way as the EVP message digest API with the addition of a key parameter.

The one major change that has been made in OpenSSL Version 0.9.7 is that you will need to zero out HMAC contexts explicitly before using them by passing them to HMAC_CTX_init . This function does not exist in previous versions of the library since HMAC_Init previously performed this initialization, although it was undocumented behavior. Once that is done, you can call HMAC_Init (HMAC_Init_ex in 0.9.7), which will properly initialize an HMAC context for use with HMAC_Update and HMAC_Final .

void HMAC_Init(HMAC_CTX *ctx, const void *key, int keylen, const EVP_MD *type);
ctx

The HMAC context object that will be initialized.

key

A buffer containing the key that will be used.

keylen

The number of bytes in the key buffer to be considered valid key data.

type

A message digest object that will be used. See Table 7-1 for a list of functions that return suitable values for this argument.

Once an HMAC context is initialized, it can be used to compute a MAC. Like the EVP API, data is passed incrementally to the HMAC_Update function.

void HMAC_Update(HMAC_CTX *ctx, const unsigned char *data, int len);
ctx

The HMAC context object that is being used to compute a MAC.

data

A buffer containing the data that will be MAC’d.

len

The number of bytes in the data buffer that will be considered valid.

All of the data can be passed to HMAC_Update at once, or it can be passed incrementally by calling the function as many times as necessary. Once all of the data that will be MAC’d has been passed into the HMAC context via HMAC_Update, calling HMAC_Final will compute the MAC and return the hash.

void HMAC_Final(HMAC_CTX *ctx, unsigned char *hash, unsigned int *len);
ctx

The HMAC context object that is being used to compute a MAC.

hash

A buffer that will receive the computed hash value. This should be at least EVP_MAX_MD_SIZE bytes in length.

len

A pointer to an integer that will receive the number of bytes written to the output hash buffer. This argument may be specified as NULL if you’re not interested in the value.

Once HMAC_Final is called, the context must either be cleaned up using HMAC_cleanup or reinitialized for reuse. In other words, after a call to HMAC_Final, you cannot use the same HMAC context object in a call to HMAC_Update or HMAC_Final without first reinitializing it. When you are finished with an HMAC context, you should always call HMAC_cleanup to properly destroy the context object and free any resources that may be associated with it. HMAC_cleanup accepts only a single argument, which is the context object to be destroyed. Example 7-6 demonstrates how to compute a MAC using HMAC_Init, HMAC_Update, and HMAC_Final.

Example 7-6. Computing a MAC using HMAC_Init, HMAC_Update, and HMAC_Final
/* Warning: DO NOT USE THIS KEY.  Generate one randomly.  This is for
 * the sake of example only.
 */
static const char key[16] = { 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 
                              0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00 };

/* Returns 0 on failure, 1 on success */
int HMAC_file_and_print(unsigned char *fname)
{
    FILE          *f = fopen(fname, "rb");
    unsigned char *contents;
    unsigned char result[EVP_MAX_MD_SIZE];
    unsigned int  flen, dlen;
    HMAC_CTX      ctx;

    if (!f)
        return 0;
    contents = read_file(f, &flen);
    fclose(f);
    if (!contents)
        return 0;

    HMAC_Init(&ctx, key, sizeof(key), EVP_sha1(  ));
    HMAC_Update(&ctx, contents, flen);
    HMAC_Final(&ctx, result, &dlen);
    HMAC_cleanup(&ctx);

    printf("HMAC(%s, ", fname);
    print_hex(key, sizeof(key));
    printf(")= ");
    print_hex(result, dlen);
    printf("\n");

    return 1;
}

Other MACs

OpenSSL has direct support only for HMAC, but there are several other kinds of MACs that are easily implemented. Some of the simplest and most useful are based on block ciphers. A large part of why HMAC is so popular is that it uses a cryptographic one-way hash as its underlying cryptographic primitive. A one-way hash was advantageous in the days when it was difficult to export strong cryptography, since true one-way functions were not restricted in any way.

However, MACs based on block ciphers can be compelling. First, such constructions can be faster, because HMAC must perform two hash operations. Second, those looking to keep total code size small will appreciate being able to reuse block cipher code (unless you specifically add the algorithm, the hash function won’t get linked in if you don’t use it). This advantage is especially appealing to people who wish to push cryptography into hardware.

HMAC does have provable security properties, but so do many cipher-based MACs, such as CBC-MAC, UMAC, and XCBC-MAC. In all cases, security proofs rely on an assumption of security in the underlying cryptographic primitive. For example, HMAC is secure, assuming that the underlying hash algorithm used with it is secure, and UMAC and XCBC-MAC are secure, assuming that the underlying block cipher is secure. It’s smart to keep security assumptions to a minimum. For example, if you use a block cipher and a hash function, a break of either is likely to break the entire system. In such a case, your system is only as secure as its weakest link.

CBC-MAC is certainly unencumbered by patents, and XCBC-MAC and XOR-MAC are probably unencumbered. Some of the theoretical work UMAC is based upon might actually be covered by patent, so use it with caution.

CBC-MAC

The simplest MAC based on a block cipher is CBC-MAC. Basically, the message to be processed is encrypted using a block cipher in CBC mode. The authentication value is the last block of the ciphertext, or part thereof. This MAC is secure, assuming that the underlying block cipher is secure, and assuming that a single key processes only messages of a fixed size (the fixed size is calculated after padding is added; padding non-block-aligned messages is necessary).

The primary limitation of CBC-MAC is that it is not parallelizable (also true of HMAC), but this is not a significant issue except in the realm of gigabit networking. Another issue, one shared with all MACs based on block ciphers, is that any party with an authentication key and a resulting value can create new messages that yield the same MAC value. This problem is usually not considered a serious drawback, but if such a problem would be worrisome in a system you are designing, then stick with HMAC.

Many MAC constructions retain their provable security properties when used with a compression function instead of a block cipher. For example, XOR-MACs such as XMCC are frequently used with MD5 as the underlying cryptographic primitive. This can help solve the reversibility problem.

In Example 7-7, we provide a header file for a CBC-MAC implementation, which should be placed in a file named cbcmac.h.

Example 7-7. cbcmac.h
#ifndef CBC_MAC_H_  _
#define CBC_MAC_H_  _

#include <openssl/evp.h>
#include <stdlib.h>


typedef struct CBCMAC_CTX_st
{
    EVP_CIPHER_CTX cctx;
    char           cbcstate[CBCMAC_MAX_BYTES];
    char           workspace[CBCMAC_MAX_BYTES];
    short          worklen;
} CBCMAC_CTX;

int CBCMAC_Init(CBCMAC_CTX *mctx, EVP_CIPHER *c, const unsigned char *k);
int CBCMAC_Update(CBCMAC_CTX *mctx, const char *data, int len);
int CBCMAC_Final(CBCMAC_CTX *mctx, unsigned char *out, int *outl);
int CBCMAC(EVP_CIPHER *c, const char *key, int key_len, 
       unsigned char *str, int sz, unsigned char *out, int *outlen);

#endif

The above API is similar to the HMAC API. The context data type is obviously different, and the user is expected to pass in a block cipher object instead of a message digest object. Note that the block cipher must be in ECB mode, even though we’re using CBC-MAC. The reason for this is that the above code implements the CBC mode itself, without saving encrypted blocks. Also, simply running a block cipher in CBC mode is not interoperable in cases in which messages need to be padded, because PKCS block cipher padding is different from the standard CBC-MAC padding. In this example, we’re using ECB mode to implement a more secure mode of operation, so don’t take this use of ECB mode as an endorsement in the general case!

Another difference between the CBC-MAC API and the HMAC API is that CBC-MAC does not require the user to pass in the key length explicitly. The implementation simply reads in the number of bytes that corresponds with the selected cipher.

Note that we recommend using AES when using CBC-MAC, assuming you are using OpenSSL 0.9.7 or later.

Example 7-8 shows the actual implementation of CBC-MAC.

Example 7-8. cbcmac.c
#include "cbcmac.h"

int CBCMAC_Init(CBCMAC_CTX *mctx, EVP_CIPHER *c, const unsigned char *k)
{
    int i, bl;

    EVP_EncryptInit(&(mctx->cctx), c, (unsigned char *)k, 0);
    if (EVP_CIPHER_CTX_mode(&(mctx->cctx)) != EVP_CIPH_ECB_MODE) 
        return -1;
    mctx->worklen = 0;
    bl = EVP_CIPHER_CTX_block_size(&(mctx->cctx));
    for (i = 0;  i < bl;  i++)
        mctx->cbcstate[i] = 0;
    return 0;
}

/* We hand implement CBC-mode because of the requirements for the last block,
 * and to avoid dynamic memory allocation.
 */
int CBCMAC_Update(CBCMAC_CTX *mctx, const char *data, int len)
{
    int bl, i, n = 0, outl;

    bl = EVP_CIPHER_CTX_block_size(&(mctx->cctx));

    if (mctx->worklen)
    {
        n = bl - mctx->worklen;
        if (n > len) /* Not enough bytes passed in to fill block buffer. */
        {
            for (i = 0;  i < len;  i++)
                mctx->workspace[mctx->worklen + i] = data[i];
            mctx->worklen += len;
            return 0;
        }
        else
        {
            for (i = 0;  i < n;  i++)
                mctx->workspace[mctx->worklen + i] = data[i] ^ mctx->cbcstate[i];
            EVP_EncryptUpdate(&(mctx->cctx), mctx->cbcstate, &outl, 
                             mctx->workspace, bl);
        }
    }
    while (n < len)
    {
        for (i = 0;  i < bl;  i++)
            mctx->workspace[i] = data[n + i] ^ mctx->cbcstate[i];
        n = n + bl;
        EVP_EncryptUpdate(&(mctx->cctx), mctx->cbcstate, &outl, 
                         mctx->workspace, bl);
    }
    mctx->worklen = len - n;
    for (i = 0;  i < mctx->worklen;  i++)
        mctx->workspace[i] = data[n + i];
    return 0;
}

int CBCMAC_Final(CBCMAC_CTX *mctx, unsigned char *out, int *outl)
{
    int i, bl = EVP_CIPHER_CTX_block_size(&(mctx->cctx));

    /* Pad with null bytes if necessary. In reality, we just copy in the
     * CBC state, since x ^ 0 = x. 
     */
    if (mctx->worklen)
    {
        for (i = mctx->worklen;  i < bl;  i++)
            mctx->workspace[i] = mctx->cbcstate[i];
        EVP_EncryptUpdate(&(mctx->cctx), out, outl, mctx->workspace, bl);
    }
    else
    {
        for (i = 0;  i < bl;  i++)
            out[i] = mctx->cbcstate[i];
        *outl = bl;
    }
    return 0;
}

int CBCMAC(EVP_CIPHER *c, const char *key, int key_len, unsigned char *str,
       int sz, unsigned char *out, int *outlen)
{
    CBCMAC_CTX x;
    int        e;

    if ((e = CBCMAC_Init(&x, c, key)))
        return e;
    if ((e = CBCMAC_Update(&x, str, sz)))
        return e;
    return CBCMAC_Final(&x, out, outlen);
}

XCBC-MAC

Black and Rogaway developed a simple modification to CBC-MAC that can process varying length messages with a single key, called XCBC-MAC. The basic idea is to run CBC-MAC as normal until it comes time to encrypt the last block. Before that encryption occurs, one of two supplemental keys is XOR’d into the “plaintext,” depending on the message length. This MAC is not noticeably slower than CBC-MAC, since it requires only a single additional XOR operation. Example 7-9 demonstrates XCBC-MAC.

Example 7-9. xcbcmac.h
#ifndef XCBC_MAC_H_  _
#define XCBC_MAC_H_  _

#include <openssl/evp.h>
#include <stdlib.h>

#define XCBC_MAX_BYTES   32

typedef struct XCMAC_CTX_st 
{
    EVP_CIPHER_CTX cctx;
    char           dk1[XCBC_MAX_BYTES];
    char           dk2[XCBC_MAX_BYTES];
    char           dk3[XCBC_MAX_BYTES];
    char           cbcstate[XCBC_MAX_BYTES];
    char           workspace[XCBC_MAX_BYTES];
    short          worklen;
    short          started;
} XCMAC_CTX;

int XCMAC_Init(XCMAC_CTX *mctx, EVP_CIPHER *c, const unsigned char *k);
int XCMAC_Update(XCMAC_CTX *mctx, const char *data, int len);
int XCMAC_Final(XCMAC_CTX *mctx, unsigned char *out, int *outl);
int XCMAC(EVP_CIPHER *c, const char *key, unsigned char *str, int sz,
          unsigned char *out, int *outlen);

#endif

Example 7-9 shows an API for XCBC-MAC, which is implemented in Example 7-10. The API is identical to our CBC-MAC API, with the exception of a different context type.

While XCBC-MAC uses three keys, it generates them from a single master key. The derived keys are computed by encrypting three fixed values with the original key, one value for each derived key. The output of each encryption is the same size as the cipher’s block length. That’s fine for the second two derived keys, because they are simply XOR’d into blocks of data. However, a single block may not be long enough for the first derived key because it is used in the block cipher, which may require a key that is longer than the block size.

The only specified instance of XCBC-MAC we’ve seen to date uses AES with 128-bit keys and 128-bit blocks, which obviously don’t have this problem. The original description of XCBC-MAC describes what to do at a high level. Basically, you just perform more encryptions with the master key until enough data is generated. The only trick is that you need to use a unique plaintext for each encryption. In our implementation below, we allow for equal block and key sizes, as well as the common cases in which key length is twice the block length. Attempting to use any other block cipher will cause an error to be returned. When run with unequal block sizes and key sizes, this implementation is not guaranteed to interoperate with any other implementation you may find.

Example 7-10. xcbcmac.c
#include "xcbcmac.h"

/* These are recommended by Rogaway. */
static char g1[XCBC_MAX_BYTES] = 
{
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01 
};

static char g2[XCBC_MAX_BYTES] = 
{
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02 
};

static char g3[XCBC_MAX_BYTES] = 
{
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03 
};

/* This is the extra plaintext for when generating the second half of a key  
 * when the block size is half the key length.
 */
static char g4[XCBC_MAX_BYTES] = 
{
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04
};

int XCMAC_Init(XCMAC_CTX *mctx, EVP_CIPHER *c, const unsigned char *k)
{ 
    EVP_CIPHER_CTX tctx;
    int            i, outl, bl, kl;

    EVP_EncryptInit(&tctx, c, (unsigned char *)k, 0);

    kl = EVP_CIPHER_CTX_key_length(&tctx);
    bl = EVP_CIPHER_CTX_block_size(&tctx);

    if (kl != bl && bl * 2 != kl)
        return -1;
    EVP_EncryptUpdate(&tctx, mctx->dk1, &outl, g1, bl);

    if (kl != bl)
        EVP_EncryptUpdate(&tctx, &(mctx->dk1[bl]), &outl, g4, bl);
    EVP_EncryptUpdate(&tctx, mctx->dk2, &outl, g2, bl);
    EVP_EncryptUpdate(&tctx, mctx->dk3, &outl, g3, bl);
  
    EVP_EncryptInit(&(mctx->cctx), c, mctx->dk1, 0);

    if (EVP_CIPHER_CTX_mode(&(mctx->cctx)) != EVP_CIPH_ECB_MODE) 
        return -2;

    mctx->worklen = 0;
    mctx->started = 0;
    for (i = 0;  i < bl;  i++) 
        mctx->cbcstate[i] = 0;
    return 0;
}

int XCMAC_Update(XCMAC_CTX *mctx, const char *data, int len) 
{
    int bl, i, n = 0, outl;

    if (!len) 
        return 0;

    bl = EVP_CIPHER_CTX_block_size(&(mctx->cctx));
    for (i = 0;  i < len;  i++) 
    {
        if (!mctx->worklen && mctx->started) 
            EVP_EncryptUpdate(&(mctx->cctx), mctx->cbcstate, &outl,
                             mctx->workspace, bl);
        else 
            mctx->started = 1;
        mctx->workspace[mctx->worklen] = data[n++] ^ mctx->cbcstate[mctx->worklen];
        mctx->worklen++;
        mctx->worklen %= bl;
    }
    return 0;
}

int XCMAC_Final(XCMAC_CTX *mctx, unsigned char *out, int *outl) 
{
    int i, bl = EVP_CIPHER_CTX_block_size(&(mctx->cctx));

    if (!mctx->started) 
        return -1;
    if (mctx->worklen) 
    {
        /* Pad and XOR with K2, then encrypt */
        mctx->workspace[mctx->worklen] = 0x90 ^ mctx->cbcstate[mctx->worklen];
        for (i = mctx->worklen + 1;  i < bl;  i++) 
            mctx->workspace[i] = mctx->cbcstate[mctx->worklen]; /* ^ 0 */
        for (i = 0;  i < bl;  i++) 
            mctx->workspace[i] ^= mctx->dk2[i];
    } 
    else 
    {
        /* XOR with K3, then encrypt. */
        for (i = 0;  i < bl;  i++) 
            mctx->workspace[i] ^= mctx->dk3[i];
    }
    EVP_EncryptUpdate(&(mctx->cctx), out, outl, mctx->workspace, bl);
    return 0;
}

int XCMAC(EVP_CIPHER *c, const char *key, unsigned char *str, int sz, 
      unsigned char *out, int *outlen) 
{
    XCMAC_CTX x;
    int       e;

    if ((e = XCMAC_Init(&x, c, key))) 
        return e; 
    if ((e = XCMAC_Update(&x, str, sz))) 
        return e; 
    return XCMAC_Final(&x, out, outlen);
}

Note that the padding scheme in the above implementation of XCBC-MAC is different from the one used by CBC-MAC, which pads to the nearest block length with null bytes. The one used here is the one that is recommended by the algorithm’s authors and is used in other implementations. In this scheme, the pad is all zeros, except for the first bit, which is set to one.

XOR-MAC

XOR MACs are a family of message authentication algorithms that are based on a block cipher and are highly parallelizable, and thus suitable for authenticating traffic on a gigabit network. If you’re not worried about potential parallelism, then you should probably use one of the other MACs we discuss in this chapter.

There are two specified XOR-MACs. The only one we have seen used is XMACC, which uses counter mode encryption. We provide a sequential implementation of this algorithm on the book’s web site.

UMAC

UMAC is an incredibly fast MAC based on the mathematical concept of universal functions. It is provably secure if the underlying block cipher used by the algorithm is secure. UMAC is not parallelizable, but an implementation running on a current high-end processor can handle over half a gigabyte of data per second.

The IETF IPSec working group is considering adopting it as a standard, but its adoption is being held up due to potential intellectual property problems. The authors of UMAC have released any claims they have to intellectual property on that algorithm, but, as of this writing, there is significant concern that there may be a patent covering some of the underlying primitives. If that turns out to be the case, using UMAC would potentially require paying a licensing fee. If you do use this algorithm, be attentive to its status, and change quickly if you are unwilling to license. As we learn new information about this topic, we will update the book’s web site.

See the UMAC home page for more information and reference code: http://www.cs.ucdavis.edu/~rogaway/umac/.

Secure HTTP Cookies

Let’s pull our knowledge of symmetric cryptography and message authentication codes together in a real application, namely setting cookies over HTTP in a user’s web browser from a server-side application. Web cookies are implemented by setting a value in the MIME header sent to the client in a server response. If the client accepts the cookie, then it will present the cookie back to the server every time the specified conditions are met.

A single MIME header is a header name followed by a colon, a space, and then the header value. The format of the header value depends on the header name. In this example, we’re concerned with only two headers: the Set-Cookie header, which can be sent to the client when presenting a web page, and the Cookie header, which the client presents to the server when the user browses to a site for which a cookie is stored.

Let’s consider an example in which we want to keep track of some history of the user’s activity on our site, but we don’t want the user to look at or modify the data. To do this, we should place a cookie on the user’s machine that contains the history information. If this will be done in plaintext, we might send the following MIME header:

Set-Cookie: history=231337+13457;path=/

The path variable specifies the root page in the domain from which the cookie came. The cookie will be sent with a page request only if it is rooted under the specified path. In the above instance, the client will return this cookie to any page in the same domain. For the purposes of our example, our cookies will not persist. That is, once the user shuts down his browser, the cookies will be gone forever.

The problem with the above cookie is that the user can see and modify the contents. Instead, we should store two cookies, one containing the encrypted history information, and a second containing a MAC of the history information. The server does encoding and such when setting a cookie, then decrypts and validates whenever the cookie comes back. The server does not share its keys with any other entity—it alone uses them to ensure data has not been read or modified since it originally left the server.

It doesn’t really matter if we use a MAC computed over the ciphertext or the plaintext. The primary difference between the two is that MACing the encrypted text would allow a third party with the MAC key to authenticate message integrity without being able to read the actual message. If you have no use for this feature, and you’re at all afraid of the MAC key being stolen, then MAC the plaintext. You can even concatenate the MAC to the plaintext and encrypt everything.

One important thing when using MACs with encryption: you should never use the same key for encryption as for MACing. Indeed, in the following example, we will MAC the plaintext with one key, and encrypt the plaintext with a second key. Each result will be sent in its own cookie. The first will be called encrypted-history, and the second will be called history-mac.

The problem we encounter is that we can use only a limited character set in cookie headers, yet the output of our cryptographic algorithms is always binary. To solve this problem, we encode the binary data into the base64 character set. The base64 character set uses the uppercase letters, the lowercase letters, the numbers, and a few pieces of punctuation to represent data. Out of necessity, the length of data grows considerably when base64 encoded. We can use the EVP function EVP_EncodeBlock for base64 encoding to suit our purposes.

Example 7-11 shows part of a server-side infrastructure for setting these cookies. We assume that there is a single server process running continually that maintains state such as the global MAC key and the global encryption key. Our example produces the entire MIME-formatted cookie, but does not write the cookie into an actual message.

Example 7-11. Encrypting data for storage in a cookie
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>

#define MAC_KEY_LEN 16

static char bf_key[EVP_MAX_KEY_LENGTH];
static char iv[EVP_MAX_BLOCK_LENGTH] = {0,}; /* #define EVP_MAX_BLOCK_LENGTH
                                              * to 64 for OpenSSL 0.9.6c and
                                              * earlier.
                                              */
static char mac_key[MAC_KEY_LEN];

/* A helper function for base64 encoding */
unsigned char *base64_encode(unsigned char *buf, unsigned int len)
{
    unsigned char *ret;
    unsigned int  b64_len;

    /* the b64data to data ratio is 3 to 4.
     * integer divide by 3 then multiply by 4, add one for NULL terminator.
     */
    b64_len = (((len + 2) / 3) * 4) + 1;
    ret = (unsigned char *)malloc(b64_len);
    EVP_EncodeBlock(ret, buf, len);
    ret[b64_len - 1] = 0;
    return ret;
}

void init_keys(void)
{
    RAND_pseudo_bytes(bf_key, EVP_MAX_KEY_LENGTH);
    RAND_pseudo_bytes(mac_key, MAC_KEY_LEN);
}

static unsigned char *encrypt_input(unsigned char *inp, int *len)
{
    EVP_CIPHER_CTX ctx;
    unsigned char  *res = (unsigned char *)malloc(strlen(inp) + 
                                                  EVP_MAX_BLOCK_LENGTH);
    unsigned int   tlen;

    EVP_EncryptInit(&ctx, EVP_bf_cbc(  ), bf_key, iv);
    EVP_EncryptUpdate(&ctx, res, &tlen, inp, strlen(inp));
    *len = tlen;
    EVP_EncryptFinal(&ctx, &res[tlen], &tlen);
    *len += tlen;
    return res;
}

static char *fmt = "Set-Cookie: encrypted-history=%s;path=/\r\n"
                   "Set-Cookie: history-mac=%s;path=/\r\n";

char *create_cookies(char *hist)
{
    unsigned int  ctlen;  /* Length of cipher text in binary */
    unsigned int  maclen; /* Length of HMAC output in binary */
    unsigned char rawmac[EVP_MAX_MD_SIZE];
    unsigned char *buf, *ct, b64_hist, *b64_mac;

    /* Enough room for everything. */
    buf = (unsigned char *)malloc(strlen(fmt) + (strlen(hist) * 4) / 3 + 1 +
                                  (EVP_MAX_MD_SIZE * 4) / 3 + 1);
    ct = encrypt_input(hist, &ctlen);
    HMAC(EVP_sha1(  ), mac_key, MAC_KEY_LEN, hist, strlen(hist), rawmac, &maclen);

    b64_hist = base64_encode(ct, ctlen);
    b64_mac  = base64_encode(rawmac, maclen);
    sprintf(buf, fmt, b64_hist, b64_mac);

    free(b64_mac);
    free(b64_hist);
    return buf;
}

The function init_keys should be called once at startup. The keys remain valid until the server is restarted. The function create_cookies takes the history string as an input, then dynamically allocates a string into which properly formatted, base64-encoded text is placed. That string is returned as the result from create_cookies. The server uses 128-bit Blowfish in CBC mode as the cipher, and HMAC-SHA1 for message authentication.

In Example 7-12, we show how to take the cookie data, remove the base64 encoding, decrypt the ciphertext, and authenticate the result. The function decrypt_and_auth takes the raw base64-encoded strings for the encrypted history string and the MAC value (not the full cookie—we assume the relevant data has been parsed out, for simplicity’s sake), along with a pointer to an unsigned integer, into which the length of the decrypted results will be written. We recalculate the MAC, comparing against the returned one. The function returns the decrypted value on success, and NULL on error.

Example 7-12. Decrypting data stored in a cookie
unsigned char *base64_decode(unsigned char *bbuf, unsigned int *len)
{
    unsigned char *ret;
    unsigned int  bin_len;

    /* integer divide by 4 then multiply by 3, its binary so no NULL */
    bin_len = (((strlen(bbuf) + 3) / 4) * 3);
    ret = (unsigned char *)malloc(bin_len);
    *len = EVP_DecodeBlock(ret, bbuf, strlen(bbuf));
    return ret;
}    

static unsigned char *decrypt_history(unsigned char *ctext, int len)
{
    EVP_CIPHER_CTX ctx;
    unsigned int   tlen, tlen2;
    unsigned char  *res = (unsigned char *)malloc(len + 1);

    EVP_DecryptInit(&ctx, EVP_bf_cbc(  ), bf_key, iv);
    EVP_DecryptUpdate(&ctx, res, &tlen, ctext, len);
    EVP_DecryptFinal(&ctx, &res[tlen], &tlen2);
    res[tlen + tlen2] = 0;
    return res;
}

unsigned char *decrypt_and_auth(unsigned char *b64_hist, unsigned char *b64_mac)
{
    unsigned char *ctext, *mac1, *res, mac2[EVP_MAX_MD_SIZE];
    unsigned int  mac1len, mac2len, ctextlen;

    if (!(ctext = base64_decode(b64_hist, &ctextlen)))
        return NULL;
    if (!(mac1 = base64_decode(b64_mac, &mac1len)))
    {
        free(ctext);
        return NULL;
    }

    res = decrypt_history(ctext, ctextlen);
    HMAC(EVP_sha1(  ), mac_key, MAC_KEY_LEN, res, strlen(hist), mac2, &mac2len);
    if (binary_cmp(mac1, mac1len, mac2, mac2len))
    {
        free(res);
        res = NULL;
    }

    free(macl);
    free(ctext);

    return res;
}

Note that when you are using this infrastructure for cookie encryption, you should place a user ID and potentially a sequence number at the beginning of any text that you encrypt, then check those on decryption. Doing so will help prevent capture-replay and dictionary attacks.