Tải bản đầy đủ - 0 (trang)
14 Using a MAC That’s Optimized for Software Speed

14 Using a MAC That’s Optimized for Software Speed

Tải bản đầy đủ - 0trang

Discussion

Be sure to look at our generic recommendations for using a MAC (see

Recipe 6.9).



The hash127 algorithm is a universal hash function that can be turned into a secure

MAC using AES. It is available from Dan Bernstein’s web page: http://cr.yp.to/

hash127.html. Follow the directions on how to install the hash127 library. Once the

library is compiled, just include the directory containing hash127.h in your include

path and link against hash127.a.

Unfortunately, at the time of this writing, the hash127 implementation has not been ported to Windows. Aside from differences in inline

assembler syntax between GCC and Microsoft Visual C++, some constants used in the implementation overflow Microsoft Visual C++'s

internal token buffer. When a port becomes available, we will update

the book’s web site with the relevant information.



The way to use hash127 as a MAC is to hash the message you want to authenticate

(the hash function takes a key and a nonce as inputs, as well as the message), then

encrypt the result of the hash function using AES.

In this recipe, we present an all-in-one MAC API based on hash127, which we call

MAC127. This construction first hashes a message using hash127, then uses two

constant-time postprocessing operations based on AES. The postprocessing operations give this MAC excellent provable security under strong assumptions.

When initializing the MAC, a 16-byte key is turned into three 16-byte keys by AESencrypting three constant values. The first two derived keys are AES keys, used for

postprocessing. The third derived key is the hash key (though the hash127 algorithm will actually ignore one bit of this key).

Note that Bernstein’s hash127 interface has some practical limitations:

• The entire message must be present at the time hash127( ) is called. That is,

there’s no incremental interface. If you need a fast incremental MAC, use CMAC

(discussed in Recipe 6.13) instead.

• The API takes an array of 32-bit values as input, meaning that it cannot accept

an arbitrary character string.

However, we can encode the leftover bytes of input in the last parameter passed to

hash127( ). Bernstein expects the last parameter to be used for additional per-message keying material. We’re not required to use that parameter for keying material

(i.e., our construction is still a secure MAC). Instead, we encode any leftover bytes,

then unambiguously encode the length of the message.



288



|



Chapter 6: Hashes and Message Authentication

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



To postprocess, we encrypt the hash output with one AES key, encrypt the nonce

with the other AES key, then XOR the two ciphertexts together. This gives us provable security with good assumptions, plus the additional benefits of a nonce (see

Recipe 6.12).

The core MAC127 data type is SPC_MAC127_CTX. There are only two functions: one to

initialize a context, and one to MAC a message. The initialization function has the

following signature:

void spc_mac127_init(SPC_MAC127_CTX *ctx, unsigned char *key);



This function has the following arguments:

ctx



Context object that holds key material so that several messages may be MAC’d

with a single key.

key



Buffer that contains a 16-byte key.

To MAC a message, we use the function spc_mac127( ):

void spc_mac127(SPC_MAC127_CTX *ctx, unsigned char *m, size_t l,

unsigned char *nonce, unsigned char *out);



This function has the following arguments:

ctx



Context object to be used to perform the MAC.

m



Buffer that contains the message to be authenticated.

l



Length of the message buffer in octets.

nonce



Buffer that contains a 16-byte value that must not be repeated.

out



Buffer into which the output will be placed. It must be at least 16 bytes in size.

No more than 16 bytes will ever be written to it.

Here is our implementation of MAC127:

#include

#ifndef WIN32

#include

#include

#include

#else

#include

#include

#endif

#include













Using a MAC That’s Optimized for Software Speed | 289

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



typedef struct {

struct hash127 hctx;

SPC_KEY_SCHED ekey;

SPC_KEY_SCHED nkey;

} SPC_MAC127_CTX;

void spc_mac127_init(SPC_MAC127_CTX *ctx, unsigned char key[16]) {

int

i;

unsigned char

pt[16] = {0, };

volatile int32

hk[4];

volatile unsigned char ek[16], nk[16];

SPC_ENCRYPT_INIT(&(ctx->ekey), key, 16);

SPC_DO_ENCRYPT(&(ctx->ekey), pt, (unsigned char *)ek);

pt[15] = 1;

SPC_DO_ENCRYPT(&(ctx->ekey), pt, (unsigned char *)nk);

pt[15] = 2;

SPC_DO_ENCRYPT(&(ctx->ekey), pt, (unsigned char *)hk);

SPC_ENCRYPT_INIT(&(ctx->ekey), (unsigned char *)ek, 16);

SPC_ENCRYPT_INIT(&(ctx->nkey), (unsigned char *)nk, 16);

hk[0] = htonl(hk[0]);

hk[1] = htonl(hk[1]);

hk[2] = htonl(hk[2]);

hk[3] = htonl(hk[3]);

hash127_expand(&(ctx->hctx), (int32 *)hk);

hk[0] = hk[1] = hk[2] = hk[3] = 0;

for (i = 0; i < 16; i++) ek[i] = nk[i] = 0;

}

void spc_mac127(SPC_MAC127_CTX *c, unsigned char *msg, size_t mlen,

unsigned char nonce[16], unsigned char out[16]) {

int

i, r = mlen % 4; /* leftover bytes to stick into final block */

int32 x[4] = {0,};

for (i = 0; i
x[3] = (int32)mlen;

hash127_little((int32 *)out, (int32 *)msg, mlen / 4, &(c->hctx), x);

x[0] = htonl(*(int *)out);

x[1] = htonl(*(int *)(out + 4));

x[2] = htonl(*(int *)(out + 8));

x[3] = htonl(*(int *)(out + 12));

SPC_DO_ENCRYPT(&(c->ekey), out, out);

SPC_DO_ENCRYPT(&(c->nkey), nonce, (unsigned char *)x);

((int32 *)out)[0] ^= x[0];

((int32 *)out)[1] ^= x[1];

((int32 *)out)[2] ^= x[2];

((int32 *)out)[3] ^= x[3];

}



See Also

• hash127 home page: http://cr.yp.to/hash127.html

• Recipes 6.9, 6.12, 6.13

290



|



Chapter 6: Hashes and Message Authentication

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



6.15 Constructing a Hash Function from a Block

Cipher

Problem

You’re in an environment in which you’d like to use a hash function, but you would

prefer to use one based on a block cipher. This might be because you have only a

block cipher available, or because you would like to minimize security assumptions

in your system.



Solution

There are several good algorithms for doing this. We present one, Davies-Meyer,

where the digest size is the same as the block length of the underlying cipher. With

64-bit block ciphers, Davies-Meyer does not offer sufficient security unless you add a

nonce, in which case it is barely sufficient. Even with AES-128, without a nonce,

Davies-Meyer is somewhat liberal when you consider birthday attacks.

Unfortunately, there is only one well-known scheme worth using for converting a

block cipher into a hash function that outputs twice the block length (MDC-2), and

it is patented at the time of this writing. However, those patent issues will go away

by August 28, 2004. MDC-2 is covered in Recipe 6.16.

Note that such constructs assume that block ciphers resist related-key attacks. See

Recipe 6.3 for a general comparison of such constructs compared to dedicated constructs like SHA1.



Discussion

Hash functions do not provide security in and of themselves! If you

need to perform message integrity checking, use a MAC instead.



The Davies-Meyer hash function uses the message to hash as key material for the

block cipher. The input is padded, strengthened, and broken into blocks based on

the key length, each block used as a key to encrypt a single value. Essentially, the

message is broken into a series of keys.

With Davies-Meyer, the first value encrypted is an initialization vector (IV) that is

usually agreed upon in advance. You may treat it as a nonce instead, however, which

we strongly recommend. (The nonce is then as big as the block size of the cipher.)

The result of encryption is XOR’d with the IV, then used as a new IV. This is

repeated until all keys are exhausted, resulting in the hash output. See Figure 6-1 for

a visual description of one pass of Davies-Meyer.

Constructing a Hash Function from a Block Cipher | 291

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



IVi–1

Pi



E



IVi

Figure 6-1. The Davies-Meyer construct



Traditionally, hash functions pad by appending a bit with a value of 1, then however

many zeros are necessary to align to the next block of input. Input is typically

strengthened by adding a block of data to the end that encodes the message length.

Nonetheless, such strengthening does not protect against length-extension attacks.

(To prevent against those, see Recipe 6.7.)

Matyas-Meyer-Oseas is a similar construction that is preferable in that the plaintext

itself is not used as the key to a block cipher (this could make related-key attacks on

Davies-Meyer easier); we’ll present that as a component when we show how to

implement MDC-2 in Recipe 6.16.

Here is an example API for using Davies-Meyer wihtout a nonce:

void spc_dm_init(SPC_DM_CTX *c);

void spc_dm_update(SPC_DM_CTX *c, unsigned char *msg, size_t len);

void spc_dm_final(SPC_DM_CTX *c, unsigned char out[SPC_BLOCK_SZ]);



The following is an implementation using AES-128. This code requires linking

against an AES implementation, and it also requires that the macros developed in

Recipe 5.5 be defined (they bridge the API of your AES implementation with this

book’s API).

#include

#include

#ifndef WIN32

#include

#include

#include

#else

#include

#include

#endif

#define SPC_KEY_SZ 16

typedef struct {

unsigned char h[SPC_BLOCK_SZ];



292



|



Chapter 6: Hashes and Message Authentication

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



unsigned char b[SPC_KEY_SZ];

size_t

ix;

size_t

tl;

} SPC_DM_CTX;

void spc_dm_init(SPC_DM_CTX *c) {

memset(c->h, 0x52, SPC_BLOCK_SZ);

c->ix = 0;

c->tl = 0;

}

static void spc_dm_once(SPC_DM_CTX *c, unsigned char b[SPC_KEY_SZ]) {

int

i;

SPC_KEY_SCHED ks;

unsigned char tmp[SPC_BLOCK_SZ];

SPC_ENCRYPT_INIT(&ks, b, SPC_KEY_SZ);

SPC_DO_ENCRYPT(&ks, c->h, tmp);

for (i = 0; i < SPC_BLOCK_SZ / sizeof(int);

((int *)c->h)[i] ^= ((int *)tmp)[i];



i++)



}

void spc_dm_update(SPC_DM_CTX *c, unsigned char *t, size_t l) {

c->tl += l; /* if c->tl < l: abort */

while (c->ix && l) {

c->b[c->ix++] = *t++;

l--;

if (!(c->ix %= SPC_KEY_SZ)) spc_dm_once(c, c->b);

}

while (l > SPC_KEY_SZ) {

spc_dm_once(c, t);

t += SPC_KEY_SZ;

l -= SPC_KEY_SZ;

}

c->ix = l;

for (l = 0; l < c->ix; l++) c->b[l] = *t++;

}

void spc_dm_final(SPC_DM_CTX *c, unsigned char output[SPC_BLOCK_SZ]) {

int i;

c->b[c->ix++] = 0x80;

while (c->ix < SPC_KEY_SZ) c->b[c->ix++] = 0;

spc_dm_once(c, c->b);

memset(c->b, 0, SPC_KEY_SZ - sizeof(size_t));

c->tl = htonl(c->tl);

for (i = 0; i < sizeof(size_t); i++)

c->b[SPC_KEY_SZ - sizeof(size_t) + i] = ((unsigned char *)(&c->tl))[i];

spc_dm_once(c, c->b);

memcpy(output, c->h, SPC_BLOCK_SZ);

}



Constructing a Hash Function from a Block Cipher | 293

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



See Also

Recipes 5.5, 6.3, 6.7, 6.16



6.16 Using a Block Cipher to Build a Full-Strength

Hash Function

Problem

Given a block cipher, you want to produce a one-way hash function, where finding

collisions should always be as hard as inverting the block cipher.



Solution

Use MDC-2, which is a construction that turns a block cipher into a hash function

using two Matyas-Meyer-Oseas hashes and a bit of postprocessing.



Discussion

Hash functions do not provide security in and of themselves! If you

need to perform message integrity checking, use a MAC instead.



The MDC-2 message digest construction turns an arbitrary block cipher into a oneway hash function. It’s different from Davies-Meyer and Matyas-Meyer-Oseas in that

the output of the hash function is twice the block length of the cipher. It is also protected by patent until August 28, 2004.

However, MDC-2 does use two instances of Matyas-Meyer-Oseas as components in

its construction. Matyas-Meyer-Oseas hashes block by block and uses the internal

state as a key used to encrypt each block of input. The resulting ciphertext is XOR’d

with the block of input, and the output of that operation becomes the new internal

state. The output of the hash function is the final internal state (though if the block

size is not equal to the key size, it may need to be expanded, usually by repeating the

value). The initial value of the internal state can be any arbitrary constant. See

Figure 6-2 for a depiction of how one block of the message is treated.

An issue with Matyas-Meyer-Oseas is that the cipher block size can be smaller than

the key size, so you might need to expand the internal state somehow before using it

to encrypt. Simply duplicating part of the key is sufficient. In the code we provide

with this recipe, though, we’ll assume that you want to use AES with 128-bit keys.

Because the block size of AES is also 128 bits, there doesn’t need to be an expansion

operation.



294



|



Chapter 6: Hashes and Message Authentication

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



Pi

IVi-1



expand



E



IVi

Figure 6-2. The Mayas-Meyer-Oseas construct



MDC-2 is based on Matyas-Meyer-Oseas. There are two internal states instead of

one, and each is initialized with a different value. Each block of input is copied, and

the two copies go through one round of Matyas-Meyer-Oseas separately. Then,

before the next block of input is processed, the two internal states are shuffled a bit;

the lower halves of the two states are swapped. This is all illustrated for one block of

the message in Figure 6-3.

Pi



Li–1



Ri–1



MMO



MMO

A



B



C



D



A



D



C



B



Li



Ri



Figure 6-3. The MDC-2 construct



Clearly, input needs to be padded to the block size of the cipher. We do this internally to our implementation by adding a 1 bit to the end of the input, then as many

zeros as are necessary to make the resulting string block-aligned.

One important thing to note about MDC-2 (as well as Matyas-Meyer-Oseas) is that

there are ways to extend a message to get the same hash as a result, unless you do

something to improve the function. The typical solution is to use MD-strengthening,

which involves adding to the end of the input a block that encodes the length of the

input. We do that in the code presented later in this section.



Using a Block Cipher to Build a Full-Strength Hash Function | 295

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



Our API allows for incremental processing of messages, which means that there is a

context object. The type for our context object is named SPC_MDC2_CTX. As with other

hash functions presented in this chapter, the incremental API has three operations:

initialization, updating (where data is processed), and finalization (where the resulting hash is output).

The initialization function has the following signature:

void spc_mdc2_init(SPC_MDC2_CTX *c);



All this function does is set internal state to the correct starting values.

Processing data is actually done by the following updating function:

void spc_mdc2_update(SPC_MDC2_CTX *c, unsigned char *t, size_t l);



This function hashes l bytes located at memory address t into the context c.

The result is obtained with the following finalization function:

void spc_mdc2_final(SPC_MDC2_CTX *c, unsigned char *output);



The output argument is always a pointer to a buffer that is twice the block size of the

cipher being used. In the case of AES, the output buffer should be 32 bytes.

Following is our implementation of MDC-2, which is intended for use with AES-128.

Remember: if you want to use this for other AES key sizes or for ciphers where the

key size is different from the block size, you will need to perform some sort of key

expansion before calling SPC_ENCRYPT_INIT( ). Of course, you’ll also have to change

that call to SPC_ENCRYPT_INIT( ) to pass in the desired key length.

#include

#include

#ifndef WIN32

#include

#include

#include

#else

#include

#include

#endif

/* This implementation only works when the block size is equal to the key size */

typedef struct {

unsigned char h1[SPC_BLOCK_SZ];

unsigned char h2[SPC_BLOCK_SZ];

unsigned char bf[SPC_BLOCK_SZ];

size_t

ix;

size_t

tl;

} SPC_MDC2_CTX;

void spc_mdc2_init(SPC_MDC2_CTX *c) {

memset(c->h1, 0x52, SPC_BLOCK_SZ);

memset(c->h2, 0x25, SPC_BLOCK_SZ);



296



|



Chapter 6: Hashes and Message Authentication

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



c->ix = 0;

c->tl = 0;

}

static void spc_mdc2_oneblock(SPC_MDC2_CTX *c, unsigned char bl[SPC_BLOCK_SZ]) {

int

i, j;

SPC_KEY_SCHED ks1, ks2;

SPC_ENCRYPT_INIT(&ks1, c->h1, SPC_BLOCK_SZ);

SPC_ENCRYPT_INIT(&ks2, c->h2, SPC_BLOCK_SZ);

SPC_DO_ENCRYPT(&ks1, bl, c->h1);

SPC_DO_ENCRYPT(&ks2, bl, c->h2);

j = SPC_BLOCK_SZ / (sizeof(int) * 2);

for (i = 0; i < SPC_BLOCK_SZ / (sizeof(int) * 2);

((int *)c->h1)[i]

^= ((int *)bl)[i];

((int *)c->h2)[i]

^= ((int *)bl)[i];

((int *)c->h1)[i + j] ^= ((int *)bl)[i + j];

((int *)c->h2)[i + j] ^= ((int *)bl)[i + j];

/* Now swap the lower halves using XOR. */

((int *)c->h1)[i + j] ^= ((int *)c->h2)[i + j];

((int *)c->h2)[i + j] ^= ((int *)c->h1)[i + j];

((int *)c->h1)[i + j] ^= ((int *)c->h2)[i + j];

}



i++) {



}

void spc_mdc2_update(SPC_MDC2_CTX *c, unsigned char *t, size_t l) {

c->tl += l; /* if c->tl < l: abort */

while (c->ix && l) {

c->bf[c->ix++] = *t++;

l--;

if (!(c->ix %= SPC_BLOCK_SZ))

spc_mdc2_oneblock(c, c->bf);

}

while (l > SPC_BLOCK_SZ) {

spc_mdc2_oneblock(c, t);

t += SPC_BLOCK_SZ;

l -= SPC_BLOCK_SZ;

}

c->ix = l;

for (l = 0; l < c->ix; l++)

c->bf[l] = *t++;

}

void spc_mdc2_final(SPC_MDC2_CTX *c, unsigned char output[SPC_BLOCK_SZ * 2]) {

int i;

c->bf[c->ix++] = 0x80;

while (c->ix < SPC_BLOCK_SZ)

c->bf[c->ix++] = 0;

spc_mdc2_oneblock(c, c->bf);

memset(c->bf, 0, SPC_BLOCK_SZ - sizeof(size_t));

c->tl = htonl(c->tl);

for (i = 0; i < sizeof(size_t); i++)

c->bf[SPC_BLOCK_SZ - sizeof(size_t) + i] = ((unsigned char *)(&c->tl))[i];



Using a Block Cipher to Build a Full-Strength Hash Function | 297

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



spc_mdc2_oneblock(c, c->bf);

memcpy(output, c->h1, SPC_BLOCK_SZ);

memcpy(output+SPC_BLOCK_SZ, c->h2, SPC_BLOCK_SZ);

}



6.17 Using Smaller MAC Tags

Problem

You want to trade off security for smaller authentication tags.



Solution

Truncate the least significant bytes of the MAC, but make sure to retain adequate

security.



Discussion

Normal software environments should not have a need for smaller MACs because

space is not at a premium. However, if you’re working in a space-constrained

embedded environment, it’s acceptable to truncate MAC tags if space is a requirement. Note that doing so will not reduce computation costs. In addition, keep in

mind that security goes down as the tag size decreases, particularly if you are not

using a nonce (or are using a small nonce).



6.18 Making Encryption and Message Integrity

Work Together

Problem

You need to encrypt data and ensure the integrity of your data at the same time.



Solution

Use either an encryption mode that performs both encryption and message integrity

checking, such as CWC mode, or encrypt data with one secret key and use a second

key to MAC the encrypted data.



Discussion

Unbelievably, many subtle things can go wrong when you try to perform encryption

and message integrity checking in tandem. This is part of the reason encryption



298



|



Chapter 6: Hashes and Message Authentication

This is the Title of the Book, eMatter Edition

Copyright © 2007 O’Reilly & Associates, Inc. All rights reserved.



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

14 Using a MAC That’s Optimized for Software Speed

Tải bản đầy đủ ngay(0 tr)

×