179
return XCMAC_Finalx, 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 algorithms 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.
7.3.1.3 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
youre 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 books
web site.
7.3.1.4 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 books web site.
See the UMAC home page for more information and reference code: http:www.cs.ucdavis.edu~rogawayumac
.
7.4 Secure HTTP Cookies
Lets pull our knowledge of symmetric cryptography and message authentication codes together in a real application, namely setting cookies over HTTP in a users 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, were 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.
Lets consider an example in which we want to keep track of some history of the users activity on our site, but we dont want the user to look at or modify the data. To do this, we should place a
TE AM
FL Y
Team-Fly
®
180
cookie on the users 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 doesnt 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 youre 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 opensslevp.h include opensslhmac.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.
181
static char mac_key[MAC_KEY_LEN]; A helper function for base64 encoding
unsigned char base64_encodeunsigned 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 mallocb64_len; EVP_EncodeBlockret, buf, len;
ret[b64_len - 1] = 0; return ret;
} void init_keysvoid
{ RAND_pseudo_bytesbf_key, EVP_MAX_KEY_LENGTH;
RAND_pseudo_bytesmac_key, MAC_KEY_LEN; }
static unsigned char encrypt_inputunsigned char inp, int len {
EVP_CIPHER_CTX ctx; unsigned char res = unsigned char mallocstrleninp +
EVP_MAX_BLOCK_LENGTH; unsigned int tlen;
EVP_EncryptInitctx, EVP_bf_cbc, bf_key, iv; EVP_EncryptUpdatectx, res, tlen, inp, strleninp;
len = tlen; EVP_EncryptFinalctx, 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_cookieschar 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 mallocstrlenfmt + strlenhist 4 3 + 1 +
EVP_MAX_MD_SIZE 4 3 + 1; ct = encrypt_inputhist, ctlen;
HMACEVP_sha1, mac_key, MAC_KEY_LEN, hist, strlenhist, rawmac, maclen;
b64_hist = base64_encodect, ctlen; b64_mac = base64_encoderawmac, maclen;
182
sprintfbuf, fmt, b64_hist, b64_mac; freeb64_mac;
freeb64_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 simplicitys 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_decodeunsigned 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 = strlenbbuf + 3 4 3;
ret = unsigned char mallocbin_len; len = EVP_DecodeBlockret, bbuf, strlenbbuf;
return ret; }
static unsigned char decrypt_historyunsigned char ctext, int len {
EVP_CIPHER_CTX ctx; unsigned int tlen, tlen2;
unsigned char res = unsigned char malloclen + 1; EVP_DecryptInitctx, EVP_bf_cbc, bf_key, iv;
EVP_DecryptUpdatectx, res, tlen, ctext, len; EVP_DecryptFinalctx, res[tlen], tlen2;
res[tlen + tlen2] = 0; return res;
} unsigned char decrypt_and_authunsigned 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_decodeb64_hist, ctextlen return NULL;
if mac1 = base64_decodeb64_mac, mac1len {
183
freectext; return NULL;
} res = decrypt_historyctext, ctextlen;
HMACEVP_sha1, mac_key, MAC_KEY_LEN, res, strlenhist, mac2, mac2len;
if binary_cmpmac1, mac1len, mac2, mac2len {
freeres; res = NULL;
} freemacl;
freectext; 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.
184
Chapter 8. Public Key Algorithms
In the previous chapters, we discussed almost all of the algorithms used by the SSL protocol that make it secure. The one remaining class of algorithms is public key cryptography, which is an
essential element of protocols like SSL, SMIME, and PGP.
Depending on the algorithm employed, public key cryptography is useful for key agreement, digital signing, and encryption. Three commonly used public key algorithms are supported by
OpenSSL: Diffie-Hellman DH, DSA Digital Signature Algorithm, and RSA so named for its inventors, Rivest, Shamir, and Adleman. Its important to realize that these algorithms are not
interchangeable. Diffie-Hellman is useful for key agreement, but cannot be used for digital signatures or encryption. DSA is useful for digital signatures, but is incapable of providing key
agreement or encryption services. RSA can be used for key agreement, digital signing, and encryption.
Public key cryptography is expensive. Its strength is in the size of its keys, which are usually very large numbers. As a result, operations involving public key cryptography are slow. Most often, it
is used in combination with other cryptographic algorithms such as message digests and symmetric ciphers.
Knowing when to use public key cryptography and how to combine it securely with other cryptographic algorithms is important. Well begin with a discussion of when it is appropriate to
use public key cryptography, and when it isnt. Well continue our discussion of public key cryptography by introducing each of the three algorithms supported by OpenSSL. For each one,
well discuss what they can and cannot do, as well as provide some examples of how to access their functionality at a low level. In addition, well discuss how they can be used together to
compliment each other. Finally, well revisit our discussion of the EVP interface from Chapters 6 and 7, demonstrating how the interface can also be used with public key algorithms, which should
be the preferred method, as long as it suits your needs.
8.1 When to Use Public Key Cryptography
Suppose that we want to create a secure communications system in which a group of people can send messages to one another. Using symmetric encryption, such a system can be easily devised.
Everyone in the group agrees on a key to use when encrypting messages to each other. While this system provides data secrecy by limiting access to the messages to only those people in the group,
it also has some serious drawbacks. For example, Alice cannot send a message to Bob without Charlie also being able to read it. Additionally, Charlie could forge a message so that it appears to
have come from someone else in the group—or even worse, he could change a message that someone else sent.
In order to stop the threat of forgery or message corruption, we need to have some way to authenticate and verify the integrity of messages. At the same time, we need to provide for more
granularity in encryption to allow private messages between individuals in the group. With symmetric encryption, these goals can be met by having each of our users share a unique key with
each of the other users. With three members of the group, Alice, Bob, and Charlie, this would mean Alice has an Alice-Bob key and an Alice-Charlie key. When she receives a message from
either Bob or Charlie, she decrypts it with the appropriate key, and if she recovers a message, she has authenticated the sender. Charlie is no longer able to forge or change a message from Alice to
Bob because he does not have the Alice-Bob key.