--- /dev/null
+/*
+ * hashTime.cpp - measure performance of digest ops
+ */
+
+#include <Security/Security.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "cputime.h"
+#include "cspwrap.h"
+#include "common.h"
+#include <openssl/md5.h>
+#include <openssl/sha.h>
+#include <CommonCrypto/CommonDigest.h>
+#include "MD5.h" /* CryptKit version used Panther and prior */
+#include "SHA1.h" /* ditto */
+
+/* enumerate digest algorithms our way */
+typedef int HT_Alg;
+enum {
+ HA_MD5 = 0,
+ HA_SHA1,
+ HA_SHA224,
+ HA_SHA256,
+ HA_SHA384,
+ HA_SHA512
+};
+
+#define FIRST_ALG HA_MD5
+#define LAST_ALG HA_SHA512
+
+static void usage(char **argv)
+{
+ printf("Usage: %s [option ...]\n", argv[0]);
+ printf("Options:\n");
+ printf(" t=testspec; default=all\n");
+ printf(" test specs: c : digest context setup/teardown\n");
+ printf(" b : basic single block digest\n");
+ printf(" d : digest lots of data\n");
+ printf(" a=alg; default=all\n");
+ printf(" algs: m : MD5\n");
+ printf(" s : SHA1\n");
+ printf(" 4 : SHA224\n");
+ printf(" 2 : SHA256\n");
+ printf(" 3 : SHA384\n");
+ printf(" 5 : SHA512\n");
+ printf(" l=loops (only valid if testspec is given)\n");
+ printf(" o (use openssl implementations, MD5 and SHA1 only)\n");
+ printf(" c (use CommonCrypto implementation)\n");
+ printf(" k (use CryptKit implementations, MD5 and SHA1 only\n");
+ printf(" v verify digest by printing it\n");
+ exit(1);
+}
+
+static void dumpDigest(
+ const unsigned char *digest,
+ unsigned len)
+{
+ for(unsigned dex=0; dex<len; dex++) {
+ printf("%02X", *digest++);
+ if((dex % 4) == 3) {
+ printf(" ");
+ }
+ }
+ printf("\n");
+}
+
+/* sort-of random, but repeatable */
+static void initPtext(
+ unsigned char *ptext,
+ unsigned len)
+{
+ srandom(1);
+ for(unsigned dex=0; dex<len; dex++) {
+ *ptext++ = random();
+ }
+}
+
+/* passed to each test */
+typedef struct {
+ unsigned loops;
+ CSSM_CSP_HANDLE cspHand;
+ CSSM_ALGORITHMS algId; // MD5, SHA1
+ bool dumpDigest;
+} TestParams;
+
+/* just CDSA context setup/teardown - no CSP activity */
+static CSSM_RETURN hashContext(
+ TestParams *params)
+{
+ CSSM_CC_HANDLE ccHand;
+ CSSM_RETURN crtn;
+ unsigned loop;
+ CPUTime startTime;
+ double timeSpentMs;
+
+ startTime = CPUTimeRead();
+ for(loop=0; loop<params->loops; loop++) {
+ crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
+ params->algId, &ccHand);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = CSSM_DeleteContext(ccHand);
+ if(crtn) {
+ return crtn;
+ }
+ }
+ timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
+ printf(" context setup/delete : %u ops in %.2f ms; %f ms/op\n",
+ params->loops, timeSpentMs, timeSpentMs / (double)params->loops);
+ return CSSM_OK;
+}
+
+/* Minimal CSP init/digest/final */
+#define BASIC_BLOCK_SIZE 64 // to digest in bytes
+#define MAX_DIGEST_SIZE 64 // we provide, no malloc below CSSM
+
+static CSSM_RETURN hashBasic(
+ TestParams *params)
+{
+ CSSM_CC_HANDLE ccHand;
+ CSSM_RETURN crtn;
+ unsigned loop;
+ CPUTime startTime;
+ double timeSpentMs;
+ uint8 ptext[BASIC_BLOCK_SIZE];
+ uint8 digest[MAX_DIGEST_SIZE];
+ CSSM_DATA ptextData = {BASIC_BLOCK_SIZE, ptext};
+ CSSM_DATA digestData = {MAX_DIGEST_SIZE, digest};
+
+ /* we reuse this one inside the loop */
+ crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
+ params->algId, &ccHand);
+ if(crtn) {
+ return crtn;
+ }
+
+ /* random data, const thru the loops */
+ appGetRandomBytes(ptext, BASIC_BLOCK_SIZE);
+
+ /* start critical timing loop */
+ startTime = CPUTimeRead();
+ for(loop=0; loop<params->loops; loop++) {
+ crtn = CSSM_DigestDataInit(ccHand);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = CSSM_DigestDataUpdate(ccHand, &ptextData, 1);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = CSSM_DigestDataFinal(ccHand, &digestData);
+ if(crtn) {
+ return crtn;
+ }
+ }
+ CSSM_DeleteContext(ccHand);
+ timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
+ printf(" Digest one %u byte block : %u ops in %.2f ms; %f ms/op\n",
+ BASIC_BLOCK_SIZE, params->loops,
+ timeSpentMs, timeSpentMs / (double)params->loops);
+ return CSSM_OK;
+}
+
+/* Lots of data */
+#define PTEXT_SIZE 1000 // to digest in bytes
+#define INNER_LOOPS 1000
+
+static CSSM_RETURN hashDataRate(
+ TestParams *params)
+{
+ CSSM_CC_HANDLE ccHand;
+ CSSM_RETURN crtn;
+ unsigned loop;
+ unsigned iloop;
+ CPUTime startTime;
+ double timeSpent, timeSpentMs;
+ uint8 ptext[PTEXT_SIZE];
+ uint8 digest[MAX_DIGEST_SIZE];
+ CSSM_DATA ptextData = {PTEXT_SIZE, ptext};
+ CSSM_DATA digestData = {MAX_DIGEST_SIZE, digest};
+
+ /* we reuse this one inside the loop */
+ crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
+ params->algId, &ccHand);
+ if(crtn) {
+ return crtn;
+ }
+
+ /* random data, const thru the loops */
+ initPtext(ptext, PTEXT_SIZE);
+
+ /* start critical timing loop */
+ startTime = CPUTimeRead();
+ for(loop=0; loop<params->loops; loop++) {
+ crtn = CSSM_DigestDataInit(ccHand);
+ if(crtn) {
+ return crtn;
+ }
+ for(iloop=0; iloop<INNER_LOOPS; iloop++) {
+ crtn = CSSM_DigestDataUpdate(ccHand, &ptextData, 1);
+ if(crtn) {
+ return crtn;
+ }
+ }
+ crtn = CSSM_DigestDataFinal(ccHand, &digestData);
+ if(crtn) {
+ return crtn;
+ }
+ }
+ timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
+ timeSpent = timeSpentMs / 1000.0;
+
+ CSSM_DeleteContext(ccHand);
+ float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
+ float totalBytes = params->loops * bytesPerLoop;
+
+ /* careful, KByte = 1024, ms = 1/1000 */
+ printf(" Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
+ bytesPerLoop, params->loops,
+ timeSpentMs, timeSpentMs / (double)params->loops,
+ ((float)totalBytes / 1024.0) / timeSpent);
+ if(params->dumpDigest) {
+ dumpDigest(digest, digestData.Length);
+ }
+ return CSSM_OK;
+}
+
+/* Lots of data, openssl version */
+
+typedef union {
+ MD5_CTX md5;
+ SHA_CTX sha;
+} OS_CTX;
+
+typedef void (*initFcn)(void *digestCtx);
+typedef void (*updateFcn)(void *digestCtx, const void *data, unsigned long len);
+typedef void (*finalFcn)(unsigned char *digest, void *digestCtx);
+
+static CSSM_RETURN hashDataRateOpenssl(
+ TestParams *params)
+{
+ OS_CTX ctx;
+ initFcn initPtr = NULL;
+ updateFcn updatePtr = NULL;
+ finalFcn finalPtr = NULL;
+ unsigned loop;
+ unsigned iloop;
+ CPUTime startTime;
+ double timeSpent, timeSpentMs;
+ uint8 ptext[PTEXT_SIZE];
+ uint8 digest[MAX_DIGEST_SIZE];
+ unsigned digestLen = 16;
+
+ /* we reuse this one inside the loop */
+ switch(params->algId) {
+ case CSSM_ALGID_SHA1:
+ initPtr = (initFcn)SHA1_Init;
+ updatePtr = (updateFcn)SHA1_Update;
+ finalPtr = (finalFcn)SHA1_Final;
+ digestLen = 20;
+ break;
+ case CSSM_ALGID_MD5:
+ initPtr = (initFcn)MD5_Init;
+ updatePtr = (updateFcn)MD5_Update;
+ finalPtr = (finalFcn)MD5_Final;
+ break;
+ default:
+ printf("***Sorry, Openssl can only do SHA1 and MD5.\n");
+ return 1;
+ }
+
+ /* random data, const thru the loops */
+ initPtext(ptext, PTEXT_SIZE);
+
+ /* start critical timing loop */
+ startTime = CPUTimeRead();
+ for(loop=0; loop<params->loops; loop++) {
+ initPtr(&ctx);
+ for(iloop=0; iloop<INNER_LOOPS; iloop++) {
+ updatePtr(&ctx, ptext, PTEXT_SIZE);
+ }
+ finalPtr(digest, &ctx);
+ }
+ timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
+ timeSpent = timeSpentMs / 1000.0;
+
+ float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
+ float totalBytes = params->loops * bytesPerLoop;
+
+ /* careful, KByte = 1024, ms = 1/1000 */
+ printf(" Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
+ bytesPerLoop, params->loops,
+ timeSpentMs, timeSpentMs / (double)params->loops,
+ ((float)totalBytes / 1024.0) / timeSpent);
+ if(params->dumpDigest) {
+ dumpDigest(digest, digestLen);
+ }
+ return CSSM_OK;
+}
+
+/* Lots of data, CommonCrypto version (not thru CSP) */
+
+typedef union {
+ CC_MD5_CTX md5;
+ CC_SHA1_CTX sha;
+ CC_SHA256_CTX sha256;
+ CC_SHA512_CTX sha512;
+} CC_CTX;
+
+typedef void (*ccUpdateFcn)(void *digestCtx, const void *data, CC_LONG len);
+typedef void (*ccFinalFcn)(unsigned char *digest, void *digestCtx);
+
+static CSSM_RETURN hashDataRateCommonCrypto(
+ TestParams *params)
+{
+ CC_CTX ctx;
+ ccUpdateFcn updatePtr = NULL;
+ ccFinalFcn finalPtr = NULL;
+ initFcn initPtr = NULL;
+ unsigned loop;
+ unsigned iloop;
+ CPUTime startTime;
+ double timeSpent, timeSpentMs;
+ uint8 ptext[PTEXT_SIZE];
+ uint8 digest[MAX_DIGEST_SIZE];
+ unsigned digestLen = 16;
+
+ /* we reuse this one inside the loop */
+ switch(params->algId) {
+ case CSSM_ALGID_SHA1:
+ initPtr = (initFcn)CC_SHA1_Init;
+ updatePtr = (ccUpdateFcn)CC_SHA1_Update;
+ finalPtr = (ccFinalFcn)CC_SHA1_Final;
+ digestLen = CC_SHA1_DIGEST_LENGTH;
+ break;
+ case CSSM_ALGID_SHA224:
+ initPtr = (initFcn)CC_SHA224_Init;
+ updatePtr = (ccUpdateFcn)CC_SHA224_Update;
+ finalPtr = (ccFinalFcn)CC_SHA224_Final;
+ digestLen = CC_SHA224_DIGEST_LENGTH;
+ break;
+ case CSSM_ALGID_SHA256:
+ initPtr = (initFcn)CC_SHA256_Init;
+ updatePtr = (ccUpdateFcn)CC_SHA256_Update;
+ finalPtr = (ccFinalFcn)CC_SHA256_Final;
+ digestLen = CC_SHA256_DIGEST_LENGTH;
+ break;
+ case CSSM_ALGID_SHA384:
+ initPtr = (initFcn)CC_SHA384_Init;
+ updatePtr = (ccUpdateFcn)CC_SHA384_Update;
+ finalPtr = (ccFinalFcn)CC_SHA384_Final;
+ digestLen = CC_SHA384_DIGEST_LENGTH;
+ break;
+ case CSSM_ALGID_SHA512:
+ initPtr = (initFcn)CC_SHA512_Init;
+ updatePtr = (ccUpdateFcn)CC_SHA512_Update;
+ finalPtr = (ccFinalFcn)CC_SHA512_Final;
+ digestLen = CC_SHA512_DIGEST_LENGTH;
+ break;
+ case CSSM_ALGID_MD5:
+ initPtr = (initFcn)CC_MD5_Init;
+ updatePtr = (ccUpdateFcn)CC_MD5_Update;
+ finalPtr = (ccFinalFcn)CC_MD5_Final;
+ digestLen = CC_MD5_DIGEST_LENGTH;
+ break;
+ default:
+ printf("***BRRRZAP!\n");
+ return 1;
+ }
+
+ /* random data, const thru the loops */
+ initPtext(ptext, PTEXT_SIZE);
+
+ /* start critical timing loop */
+ startTime = CPUTimeRead();
+ for(loop=0; loop<params->loops; loop++) {
+ initPtr(&ctx);
+ for(iloop=0; iloop<INNER_LOOPS; iloop++) {
+ updatePtr(&ctx, ptext, PTEXT_SIZE);
+ }
+ finalPtr(digest, &ctx);
+ }
+ timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
+ timeSpent = timeSpentMs / 1000.0;
+
+ float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
+ float totalBytes = params->loops * bytesPerLoop;
+
+ /* careful, KByte = 1024, ms = 1/1000 */
+ printf(" Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
+ bytesPerLoop, params->loops,
+ timeSpentMs, timeSpentMs / (double)params->loops,
+ ((float)totalBytes / 1024.0) / timeSpent);
+ if(params->dumpDigest) {
+ dumpDigest(digest, digestLen);
+ }
+ return CSSM_OK;
+}
+
+/* Lots of data, CryptKit version */
+
+/* cryptkit final routines are not orthoganal, fix up here */
+static void ckSha1Final(
+ unsigned char *digest,
+ void *ctx)
+{
+ sha1GetDigest((sha1Obj)ctx, digest);
+}
+
+static void ckMD5Final(
+ unsigned char *digest,
+ void *ctx)
+{
+ MD5Final((struct MD5Context *)ctx, digest);
+}
+
+typedef void (*ckUpdateFcn)(void *digestCtx, const void *data, unsigned len);
+typedef void (*ckFinalFcn)(unsigned char *digest, void *digestCtx);
+
+static CSSM_RETURN hashDataRateCryptKit(
+ TestParams *params)
+{
+ ckUpdateFcn updatePtr = NULL;
+ ckFinalFcn finalPtr = NULL;
+ initFcn initPtr = NULL;
+ struct MD5Context md5;
+ sha1Obj sha;
+ void *ctx;
+
+ unsigned loop;
+ unsigned iloop;
+ CPUTime startTime;
+ double timeSpent, timeSpentMs;
+ uint8 ptext[PTEXT_SIZE];
+ uint8 digest[MAX_DIGEST_SIZE];
+ unsigned digestLen = 16;
+
+ /* we reuse this one inside the loop */
+ switch(params->algId) {
+ case CSSM_ALGID_SHA1:
+ sha = sha1Alloc();
+ ctx = sha;
+ initPtr = (initFcn)sha1Reinit;
+ updatePtr = (ckUpdateFcn)sha1AddData;
+ finalPtr = (ckFinalFcn)ckSha1Final;
+ digestLen = 20;
+ break;
+ case CSSM_ALGID_MD5:
+ ctx = &md5;
+ initPtr = (initFcn)MD5Init;
+ updatePtr = (ckUpdateFcn)MD5Update;
+ finalPtr = (ckFinalFcn)ckMD5Final;
+ break;
+ default:
+ printf("***Sorry, CryptKit can only do SHA1 and MD5.\n");
+ return 1;
+ }
+
+ /* random data, const thru the loops */
+ initPtext(ptext, PTEXT_SIZE);
+
+ /* start critical timing loop */
+ startTime = CPUTimeRead();
+ for(loop=0; loop<params->loops; loop++) {
+ initPtr(ctx);
+ for(iloop=0; iloop<INNER_LOOPS; iloop++) {
+ updatePtr(ctx, ptext, PTEXT_SIZE);
+ }
+ finalPtr(digest, ctx);
+ }
+ timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
+ timeSpent = timeSpentMs / 1000.0;
+
+ float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
+ float totalBytes = params->loops * bytesPerLoop;
+
+ /* careful, KByte = 1024, ms = 1/1000 */
+ printf(" Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
+ bytesPerLoop, params->loops,
+ timeSpentMs, timeSpentMs / (double)params->loops,
+ ((float)totalBytes / 1024.0) / timeSpent);
+ if(params->dumpDigest) {
+ dumpDigest(digest, digestLen);
+ }
+ return CSSM_OK;
+}
+
+typedef CSSM_RETURN (*testRunFcn)(TestParams *testParams);
+
+/*
+ * Static declaration of a test
+ */
+typedef struct {
+ const char *testName;
+ unsigned loops;
+ testRunFcn run;
+ char testSpec; // for t=xxx cmd line opt
+} TestDefs;
+
+static TestDefs testDefs[] =
+{
+ { "Digest context setup/teardown",
+ 100000,
+ hashContext,
+ 'c',
+ },
+ { "Basic single block digest",
+ 100000,
+ hashBasic,
+ 'b',
+ },
+ { "Large data digest",
+ 1000,
+ hashDataRate,
+ 'd',
+ },
+};
+
+static TestDefs testDefsOpenSSL[] =
+{
+ { "Digest context setup/teardown",
+ 100000,
+ NULL, // not implemented
+ 'c',
+ },
+ { "Basic single block digest",
+ 100000,
+ NULL, // not implemented
+ 'b',
+ },
+ { "Large data digest, OpenSSL",
+ 1000,
+ hashDataRateOpenssl,
+ 'd',
+ },
+};
+
+static TestDefs testDefsCommonCrypto[] =
+{
+ { "Digest context setup/teardown",
+ 100000,
+ NULL, // not implemented
+ 'c',
+ },
+ { "Basic single block digest",
+ 100000,
+ NULL, // not implemented
+ 'b',
+ },
+ { "Large data digest, CommonCrypto",
+ 1000,
+ hashDataRateCommonCrypto,
+ 'd',
+ },
+};
+
+static TestDefs testDefsCryptKit[] =
+{
+ { "Digest context setup/teardown",
+ 100000,
+ NULL, // not implemented
+ 'c',
+ },
+ { "Basic single block digest",
+ 100000,
+ NULL, // not implemented
+ 'b',
+ },
+ { "Large data digest, CryptKit",
+ 1000,
+ hashDataRateCryptKit,
+ 'd',
+ },
+};
+
+
+static void algToAlgId(
+ HT_Alg alg,
+ CSSM_ALGORITHMS *algId,
+ const char **algStr)
+{
+ switch(alg) {
+ case HA_MD5:
+ *algId = CSSM_ALGID_MD5;
+ *algStr = "MD5";
+ break;
+ case HA_SHA1:
+ *algId = CSSM_ALGID_SHA1;
+ *algStr = "SHA1";
+ break;
+ case HA_SHA224:
+ *algId = CSSM_ALGID_SHA224;
+ *algStr = "SHA224";
+ break;
+ case HA_SHA256:
+ *algId = CSSM_ALGID_SHA256;
+ *algStr = "SHA256";
+ break;
+ case HA_SHA384:
+ *algId = CSSM_ALGID_SHA384;
+ *algStr = "SHA384";
+ break;
+ case HA_SHA512:
+ *algId = CSSM_ALGID_SHA512;
+ *algStr = "SHA512";
+ break;
+ default:
+ printf("***algToAlgId screwup\n");
+ exit(1);
+ }
+}
+
+#define NUM_TESTS (sizeof(testDefs) / sizeof(testDefs[0]))
+
+int main(int argc, char **argv)
+{
+ TestParams testParams;
+ TestDefs *testDef;
+ TestDefs *ourTestDefs = testDefs;
+ CSSM_RETURN crtn;
+ int arg;
+ char *argp;
+ unsigned cmdLoops = 0; // can be specified in cmd line
+ // if not, use TestDefs.loops
+ char testSpec = '\0'; // allows specification of one test
+ // otherwise run all
+ HT_Alg alg;
+ const char *algStr;
+ int firstAlg = FIRST_ALG;
+ int lastAlg = LAST_ALG;
+
+ memset(&testParams, 0, sizeof(testParams));
+
+ for(arg=1; arg<argc; arg++) {
+ argp = argv[arg];
+ switch(argp[0]) {
+ case 't':
+ testSpec = argp[2];
+ break;
+ case 'l':
+ cmdLoops = atoi(&argp[2]);
+ break;
+ case 'a':
+ if(argp[1] == '\0') {
+ usage(argv);
+ }
+ switch(argp[2]) {
+ case 'm':
+ firstAlg = lastAlg = HA_MD5;
+ break;
+ case 's':
+ firstAlg = lastAlg = HA_SHA1;
+ break;
+ case '4':
+ firstAlg = lastAlg = HA_SHA224;
+ break;
+ case '2':
+ firstAlg = lastAlg = HA_SHA256;
+ break;
+ case '3':
+ firstAlg = lastAlg = HA_SHA384;
+ break;
+ case '5':
+ firstAlg = lastAlg = HA_SHA512;
+ break;
+ default:
+ usage(argv);
+ }
+ break;
+ case 'o':
+ ourTestDefs = testDefsOpenSSL;
+ break;
+ case 'c':
+ ourTestDefs = testDefsCommonCrypto;
+ break;
+ case 'k':
+ ourTestDefs = testDefsCryptKit;
+ break;
+ case 'v':
+ testParams.dumpDigest = true;
+ break;
+ default:
+ usage(argv);
+ }
+ }
+
+ testParams.cspHand = cspStartup();
+ if(testParams.cspHand == 0) {
+ printf("***Error attaching to CSP. Aborting.\n");
+ exit(1);
+ }
+
+ for(unsigned testNum=0; testNum<NUM_TESTS; testNum++) {
+ testDef = &ourTestDefs[testNum];
+
+ if(testSpec && (testDef->testSpec != testSpec)) {
+ continue;
+ }
+ if(testDef->run == NULL) {
+ continue;
+ }
+ printf("%s:\n", testDef->testName);
+ if(cmdLoops) {
+ /* user specified */
+ testParams.loops = cmdLoops;
+ }
+ else {
+ /* default */
+ testParams.loops = testDef->loops;
+ }
+ for(alg=firstAlg; alg<=lastAlg; alg++) {
+ algToAlgId(alg, &testParams.algId, &algStr);
+ printf(" === %s ===\n", algStr);
+ crtn = testDef->run(&testParams);
+ if(crtn) {
+ exit(1);
+ }
+ }
+ }
+ return 0;
+}