Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
6.5.1 Problem
You want to use a hash function to process data incrementally, returning a result when the last of the data is finally available. 6.5.2 Solution
Most hash functions use a standard interface for operation, following these steps:
6.5.3 Discussion
Libraries with cryptographic hash functions tend to support incremental operation using a standard structure. In fact, this structure is standardized for cryptographic hardware APIs in PKCS (Public Key Cryptography Standard) #11. There are four steps:
The OpenSSL API has both a single generic interface to all its hash functions and a separate API for each hash function. Here's an example using the SHA1 API: #include <stdio.h> #include <string.h> #include <openssl/sha.h> int main(int argc, char *argv[ ]) { int i; SHA_CTX ctx; unsigned char result[SHA_DIGEST_LENGTH]; /* SHA1 has a 20-byte digest. */ unsigned char *s1 = "Testing"; unsigned char *s2 = "...1...2...3..."; SHA1_Init(&ctx); SHA1_Update(&ctx, s1, strlen(s1)); SHA1_Update(&ctx, s2, strlen(s2)); /* Yes, the context object is last. */ SHA1_Final(result, &ctx); printf("SHA1(\"%s%s\") = ", s1, s2); for (i = 0; i < SHA_DIGEST_LENGTH; i++) printf("%02x", result[i]); printf("\n"); return 0; } Every hash function that OpenSSL supports has a similar API. In addition, every such function has an "all-in-one" API that allows you to combine the work of calls for initialization, updating, and finalization, obviating the need for a context object: unsigned char *SHA1(unsigned char *in, unsigned long len, unsigned char *out); This function returns a pointer to the out argument. Both the incremental API and the all-in-one API are very standard, even beyond OpenSSL. The reference versions of most hash algorithms look incredibly similar. In fact, Microsoft's CryptoAPI for Windows provides a very similar API. Any of the Microsoft CSPs provide implementations of MD2, MD5, and SHA1. The following code is the CryptoAPI version of the OpenSSL code presented previously: #include <windows.h> #include <wincrypt.h> #include <stdio.h> int main(int argc, char *argv[ ]) { BYTE *pbData; DWORD cbData = sizeof(DWORD), cbHashSize, i; HCRYPTHASH hSHA1; HCRYPTPROV hProvider; unsigned char *s1 = "Testing"; unsigned char *s2 = "...1...2...3..."; CryptAcquireContext(&hProvider, 0, MS_DEF_PROV, PROV_RSA_FULL, 0); CryptCreateHash(hProvider, CALG_SHA1, 0, 0, &hSHA1); CryptHashData(hSHA1, s1, strlen(s1), 0); CryptHashData(hSHA1, s2, strlen(s2), 0); CryptGetHashParam(hSHA1, HP_HASHSIZE, (BYTE *)&cbHashSize, &cbData, 0); pbData = (BYTE *)LocalAlloc(LMEM_FIXED, cbHashSize); CryptGetHashParam(hSHA1, HP_HASHVAL, pbData, &cbHashSize, 0); CryptDestroyHash(hSHA1); CryptReleaseContext(hProvider, 0); printf("SHA1(\"%s%s\") = ", s1, s2); for (i = 0; i < cbHashSize; i++) printf("%02x", pbData[i]); printf("\n"); LocalFree(pbData); return 0; } The preferred API for accessing hash functions from OpenSSL, though, is the EVP API, which provides a generic API to all of the hash functions OpenSSL supports. The following code does the same thing as the first example with the EVP interface instead of the SHA1 interface: #include <stdio.h> #include <string.h> #include <openssl/evp.h> int main(int argc, char *argv[ ]) { int i, ol; EVP_MD_CTX ctx; unsigned char result[EVP_MAX_MD_SIZE]; /* enough for any hash function */ unsigned char *s1 = "Testing"; unsigned char *s2 = "...1...2...3..."; /* Note the extra parameter */ EVP_DigestInit(&ctx, EVP_sha1( )); EVP_DigestUpdate(&ctx, s1, strlen(s1)); EVP_DigestUpdate(&ctx, s2, strlen(s2)); /* Here, the context object is first. Notice the pointer to the output length */ EVP_DigestFinal(&ctx, result, &ol); printf("SHA1(\"%s%s\") = ", s1, s2); for (i = 0; i < ol; i++) printf("%02x", result[i]); printf("\n"); return 0; } Note particularly that EVP_DigestFinal( ) requires you to pass in a pointer to an integer, into which the output length is stored. You should use this value in your computations instead of hardcoding SHA1's digest size, under the assumption that you might someday have to replace crypto algorithms in a hurry, in which case the digest size may change. For that reason, allocate EVP_MAX_MD_SIZE bytes for any buffer into which you store a message digest, even if some of that space may go unused. Alternatively, if you'd like to allocate a buffer of the correct size for output dynamically (which is a good idea if you're space-constrained, because if SHA-512 is ever added to OpenSSL, EVP_MAX_MD_SIZE will become 512 bits), you can use the function EVP_MD_CTX_size( ), which takes a context object and returns the size of the digest. For example: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <openssl/evp.h> int main(int argc, char *argv[ ]) { int i, ol; EVP_MD_CTX ctx; unsigned char *result; unsigned char *s1 = "Testing"; unsigned char *s2 = "...1...2...3..."; EVP_DigestInit(&ctx, EVP_sha1( )); EVP_DigestUpdate(&ctx, s1, strlen(s1)); EVP_DigestUpdate(&ctx, s2, strlen(s2)); if (!(result = (unsigned char *)malloc(EVP_MD_CTX_block_size(&ctx))))abort(); EVP_DigestFinal(&ctx, result, &ol); printf("SHA1(\"%s%s\") = ", s1, s2); for (i = 0; i < ol; i++) printf("%02x", result[i]); printf("\n"); free(result); return 0; } The OpenSSL library supports only two cryptographic hash functions that we recommend, SHA1 and RIPEMD-160. It also supports MD2, MD4, MD5, and MDC-2-DES. MDC-2-DES is reasonable, but it is slow and provides only 64 bits of resistance to birthday attacks, whereas we recommend a minimum baseline of 80 bits of security. As an alternative, you could initialize the hash function with a nonce, as discussed in Recipe 6.8. Nonetheless, Table 6-3 contains a summary of the necessary information on each hash function to use both the EVP and hash-specific APIs with OpenSSL.
Of course, you may want to use an off-the-shelf hash function that isn't supported by either OpenSSL or CryptoAPI for example, SHA-256, SHA-384, or SHA-512. Aaron Gifford has produced a good, free library with implementations of these functions and released it under a BSD-style license. It is available from http://www.aarongifford.com/computers/sha.html. That library exports an API that should look very familiar: SHA256_Init(SHA256_CTX *ctx); SHA256_Update(SHA256_CTX *ctx, unsigned char *data, size_t inlen); SHA256_Final(unsigned char out[SHA256_DIGEST_LENGTH], SHA256_CTX *ctx); SHA384_Init(SHA384_CTX *ctx); SHA384_Update(SHA384_CTX *ctx, unsigned char *data, size_t inlen); SHA384_Final(unsigned char out[SHA384_DIGEST_LENGTH], SHA384_CTX *ctx); SHA512_Init(SHA512_CTX *ctx); SHA512_Update(SHA512_CTX *ctx, unsigned char *data, size_t inlen); SHA512_Final(unsigned char out[SHA512_DIGEST_LENGTH], SHA512_CTX *ctx); All of the previous functions are prototyped in the sha2.h header file. 6.5.4 See Also
Implementations of SHA-256 and SHA-512 from Aaron Gifford: http://www.aarongifford.com/computers/sha.html Recipe 6.7, Recipe 6.8 |