1/*
2 * hashTime.cpp - measure performance of digest ops
3 */
4
5#include <Security/Security.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include "cputime.h"
10#include "cspwrap.h"
11#include "common.h"
12#include <openssl/md5.h>
13#include <openssl/sha.h>
14#include <CommonCrypto/CommonDigest.h>
15#include "MD5.h"			/* CryptKit version used Panther and prior */
16#include "SHA1.h"			/* ditto */
17
18/* enumerate digest algorithms our way */
19typedef int HT_Alg;
20enum {
21	HA_MD5 = 0,
22	HA_SHA1,
23	HA_SHA224,
24	HA_SHA256,
25	HA_SHA384,
26	HA_SHA512
27};
28
29#define FIRST_ALG	HA_MD5
30#define LAST_ALG	HA_SHA512
31
32static void usage(char **argv)
33{
34    printf("Usage: %s [option ...]\n", argv[0]);
35    printf("Options:\n");
36	printf("   t=testspec; default=all\n");
37	printf("     test specs: c : digest context setup/teardown\n");
38	printf("                 b : basic single block digest\n");
39	printf("                 d : digest lots of data\n");
40	printf("   a=alg; default=all\n");
41	printf("           algs: m : MD5\n");
42	printf("                 s : SHA1\n");
43	printf("                 4 : SHA224\n");
44	printf("                 2 : SHA256\n");
45	printf("                 3 : SHA384\n");
46	printf("                 5 : SHA512\n");
47	printf("   l=loops (only valid if testspec is given)\n");
48	printf("   o (use openssl implementations, MD5 and SHA1 only)\n");
49	printf("   c (use CommonCrypto implementation)\n");
50	printf("   k (use CryptKit implementations, MD5 and SHA1 only\n");
51	printf("   v verify digest by printing it\n");
52	exit(1);
53}
54
55static void dumpDigest(
56	const unsigned char *digest,
57	unsigned len)
58{
59	for(unsigned dex=0; dex<len; dex++) {
60		printf("%02X", *digest++);
61		if((dex % 4) == 3) {
62			printf(" ");
63		}
64	}
65	printf("\n");
66}
67
68/* sort-of random, but repeatable */
69static void initPtext(
70	unsigned char *ptext,
71	unsigned len)
72{
73	srandom(1);
74	for(unsigned dex=0; dex<len; dex++) {
75		*ptext++ = random();
76	}
77}
78
79/* passed to each test */
80typedef struct {
81	unsigned			loops;
82	CSSM_CSP_HANDLE		cspHand;
83	CSSM_ALGORITHMS		algId;		// MD5, SHA1
84	bool				dumpDigest;
85} TestParams;
86
87/* just CDSA context setup/teardown - no CSP activity */
88static CSSM_RETURN hashContext(
89	TestParams	*params)
90{
91	CSSM_CC_HANDLE	ccHand;
92	CSSM_RETURN 	crtn;
93	unsigned 		loop;
94	CPUTime 		startTime;
95	double			timeSpentMs;
96
97	startTime = CPUTimeRead();
98	for(loop=0; loop<params->loops; loop++) {
99		crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
100			params->algId, &ccHand);
101		if(crtn) {
102			return crtn;
103		}
104		crtn = CSSM_DeleteContext(ccHand);
105		if(crtn) {
106			return crtn;
107		}
108	}
109	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
110	printf("   context setup/delete : %u ops in %.2f ms; %f ms/op\n",
111		params->loops, timeSpentMs, timeSpentMs / (double)params->loops);
112	return CSSM_OK;
113}
114
115/* Minimal CSP init/digest/final */
116#define BASIC_BLOCK_SIZE	64		// to digest in bytes
117#define MAX_DIGEST_SIZE		64		// we provide, no malloc below CSSM
118
119static CSSM_RETURN hashBasic(
120	TestParams	*params)
121{
122	CSSM_CC_HANDLE	ccHand;
123	CSSM_RETURN 	crtn;
124	unsigned 		loop;
125	CPUTime 		startTime;
126	double			timeSpentMs;
127	uint8			ptext[BASIC_BLOCK_SIZE];
128	uint8			digest[MAX_DIGEST_SIZE];
129	CSSM_DATA		ptextData = {BASIC_BLOCK_SIZE, ptext};
130	CSSM_DATA		digestData = {MAX_DIGEST_SIZE, digest};
131
132	/* we reuse this one inside the loop */
133	crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
134			params->algId, &ccHand);
135	if(crtn) {
136		return crtn;
137	}
138
139	/* random data, const thru the loops */
140	appGetRandomBytes(ptext, BASIC_BLOCK_SIZE);
141
142	/* start critical timing loop */
143	startTime = CPUTimeRead();
144	for(loop=0; loop<params->loops; loop++) {
145		crtn = CSSM_DigestDataInit(ccHand);
146		if(crtn) {
147			return crtn;
148		}
149		crtn = CSSM_DigestDataUpdate(ccHand, &ptextData, 1);
150		if(crtn) {
151			return crtn;
152		}
153		crtn = CSSM_DigestDataFinal(ccHand, &digestData);
154		if(crtn) {
155			return crtn;
156		}
157	}
158	CSSM_DeleteContext(ccHand);
159	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
160	printf("   Digest one %u byte block : %u ops in %.2f ms; %f ms/op\n",
161		BASIC_BLOCK_SIZE, params->loops,
162		timeSpentMs, timeSpentMs / (double)params->loops);
163	return CSSM_OK;
164}
165
166/* Lots of data */
167#define PTEXT_SIZE			1000		// to digest in bytes
168#define INNER_LOOPS			1000
169
170static CSSM_RETURN hashDataRate(
171	TestParams	*params)
172{
173	CSSM_CC_HANDLE	ccHand;
174	CSSM_RETURN 	crtn;
175	unsigned 		loop;
176	unsigned		iloop;
177	CPUTime 		startTime;
178	double			timeSpent, timeSpentMs;
179	uint8			ptext[PTEXT_SIZE];
180	uint8			digest[MAX_DIGEST_SIZE];
181	CSSM_DATA		ptextData = {PTEXT_SIZE, ptext};
182	CSSM_DATA		digestData = {MAX_DIGEST_SIZE, digest};
183
184	/* we reuse this one inside the loop */
185	crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
186			params->algId, &ccHand);
187	if(crtn) {
188		return crtn;
189	}
190
191	/* random data, const thru the loops */
192	initPtext(ptext, PTEXT_SIZE);
193
194	/* start critical timing loop */
195	startTime = CPUTimeRead();
196	for(loop=0; loop<params->loops; loop++) {
197		crtn = CSSM_DigestDataInit(ccHand);
198		if(crtn) {
199			return crtn;
200		}
201		for(iloop=0; iloop<INNER_LOOPS; iloop++) {
202			crtn = CSSM_DigestDataUpdate(ccHand, &ptextData, 1);
203			if(crtn) {
204				return crtn;
205			}
206		}
207		crtn = CSSM_DigestDataFinal(ccHand, &digestData);
208		if(crtn) {
209			return crtn;
210		}
211	}
212	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
213	timeSpent = timeSpentMs / 1000.0;
214
215	CSSM_DeleteContext(ccHand);
216	float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
217	float totalBytes   = params->loops * bytesPerLoop;
218
219	/* careful, KByte = 1024, ms = 1/1000 */
220	printf("   Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
221		bytesPerLoop, params->loops,
222		timeSpentMs, timeSpentMs / (double)params->loops,
223		((float)totalBytes / 1024.0) / timeSpent);
224	if(params->dumpDigest) {
225		dumpDigest(digest, digestData.Length);
226	}
227	return CSSM_OK;
228}
229
230/* Lots of data, openssl version */
231
232typedef union {
233	MD5_CTX		md5;
234	SHA_CTX		sha;
235} OS_CTX;
236
237typedef void (*initFcn)(void *digestCtx);
238typedef void (*updateFcn)(void *digestCtx, const void *data, unsigned long len);
239typedef void (*finalFcn)(unsigned char *digest, void *digestCtx);
240
241static CSSM_RETURN hashDataRateOpenssl(
242	TestParams	*params)
243{
244	OS_CTX			ctx;
245	initFcn			initPtr = NULL;
246	updateFcn		updatePtr = NULL;
247	finalFcn		finalPtr = NULL;
248	unsigned 		loop;
249	unsigned		iloop;
250	CPUTime 		startTime;
251	double			timeSpent, timeSpentMs;
252	uint8			ptext[PTEXT_SIZE];
253	uint8			digest[MAX_DIGEST_SIZE];
254	unsigned		digestLen = 16;
255
256	/* we reuse this one inside the loop */
257	switch(params->algId) {
258		case CSSM_ALGID_SHA1:
259			initPtr = (initFcn)SHA1_Init;
260			updatePtr = (updateFcn)SHA1_Update;
261			finalPtr = (finalFcn)SHA1_Final;
262			digestLen = 20;
263			break;
264		case CSSM_ALGID_MD5:
265			initPtr = (initFcn)MD5_Init;
266			updatePtr = (updateFcn)MD5_Update;
267			finalPtr = (finalFcn)MD5_Final;
268			break;
269		default:
270			printf("***Sorry, Openssl can only do SHA1 and MD5.\n");
271			return 1;
272	}
273
274	/* random data, const thru the loops */
275	initPtext(ptext, PTEXT_SIZE);
276
277	/* start critical timing loop */
278	startTime = CPUTimeRead();
279	for(loop=0; loop<params->loops; loop++) {
280		initPtr(&ctx);
281		for(iloop=0; iloop<INNER_LOOPS; iloop++) {
282			updatePtr(&ctx, ptext, PTEXT_SIZE);
283		}
284		finalPtr(digest, &ctx);
285	}
286	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
287	timeSpent = timeSpentMs / 1000.0;
288
289	float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
290	float totalBytes   = params->loops * bytesPerLoop;
291
292	/* careful, KByte = 1024, ms = 1/1000 */
293	printf("   Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
294		bytesPerLoop, params->loops,
295		timeSpentMs, timeSpentMs / (double)params->loops,
296		((float)totalBytes / 1024.0) / timeSpent);
297	if(params->dumpDigest) {
298		dumpDigest(digest, digestLen);
299	}
300	return CSSM_OK;
301}
302
303/* Lots of data, CommonCrypto version (not thru CSP) */
304
305typedef union {
306	CC_MD5_CTX		md5;
307	CC_SHA1_CTX		sha;
308	CC_SHA256_CTX	sha256;
309	CC_SHA512_CTX	sha512;
310} CC_CTX;
311
312typedef void (*ccUpdateFcn)(void *digestCtx, const void *data, CC_LONG len);
313typedef void (*ccFinalFcn)(unsigned char *digest, void *digestCtx);
314
315static CSSM_RETURN hashDataRateCommonCrypto(
316	TestParams	*params)
317{
318	CC_CTX			ctx;
319	ccUpdateFcn		updatePtr = NULL;
320	ccFinalFcn		finalPtr = NULL;
321	initFcn			initPtr = NULL;
322	unsigned 		loop;
323	unsigned		iloop;
324	CPUTime 		startTime;
325	double			timeSpent, timeSpentMs;
326	uint8			ptext[PTEXT_SIZE];
327	uint8			digest[MAX_DIGEST_SIZE];
328	unsigned		digestLen = 16;
329
330	/* we reuse this one inside the loop */
331	switch(params->algId) {
332		case CSSM_ALGID_SHA1:
333			initPtr = (initFcn)CC_SHA1_Init;
334			updatePtr = (ccUpdateFcn)CC_SHA1_Update;
335			finalPtr = (ccFinalFcn)CC_SHA1_Final;
336			digestLen = CC_SHA1_DIGEST_LENGTH;
337			break;
338		case CSSM_ALGID_SHA224:
339			initPtr = (initFcn)CC_SHA224_Init;
340			updatePtr = (ccUpdateFcn)CC_SHA224_Update;
341			finalPtr = (ccFinalFcn)CC_SHA224_Final;
342			digestLen = CC_SHA224_DIGEST_LENGTH;
343			break;
344		case CSSM_ALGID_SHA256:
345			initPtr = (initFcn)CC_SHA256_Init;
346			updatePtr = (ccUpdateFcn)CC_SHA256_Update;
347			finalPtr = (ccFinalFcn)CC_SHA256_Final;
348			digestLen = CC_SHA256_DIGEST_LENGTH;
349			break;
350		case CSSM_ALGID_SHA384:
351			initPtr = (initFcn)CC_SHA384_Init;
352			updatePtr = (ccUpdateFcn)CC_SHA384_Update;
353			finalPtr = (ccFinalFcn)CC_SHA384_Final;
354			digestLen = CC_SHA384_DIGEST_LENGTH;
355			break;
356		case CSSM_ALGID_SHA512:
357			initPtr = (initFcn)CC_SHA512_Init;
358			updatePtr = (ccUpdateFcn)CC_SHA512_Update;
359			finalPtr = (ccFinalFcn)CC_SHA512_Final;
360			digestLen = CC_SHA512_DIGEST_LENGTH;
361			break;
362		case CSSM_ALGID_MD5:
363			initPtr = (initFcn)CC_MD5_Init;
364			updatePtr = (ccUpdateFcn)CC_MD5_Update;
365			finalPtr = (ccFinalFcn)CC_MD5_Final;
366			digestLen = CC_MD5_DIGEST_LENGTH;
367			break;
368		default:
369			printf("***BRRRZAP!\n");
370			return 1;
371	}
372
373	/* random data, const thru the loops */
374	initPtext(ptext, PTEXT_SIZE);
375
376	/* start critical timing loop */
377	startTime = CPUTimeRead();
378	for(loop=0; loop<params->loops; loop++) {
379		initPtr(&ctx);
380		for(iloop=0; iloop<INNER_LOOPS; iloop++) {
381			updatePtr(&ctx, ptext, PTEXT_SIZE);
382		}
383		finalPtr(digest, &ctx);
384	}
385	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
386	timeSpent = timeSpentMs / 1000.0;
387
388	float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
389	float totalBytes   = params->loops * bytesPerLoop;
390
391	/* careful, KByte = 1024, ms = 1/1000 */
392	printf("   Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
393		bytesPerLoop, params->loops,
394		timeSpentMs, timeSpentMs / (double)params->loops,
395		((float)totalBytes / 1024.0) / timeSpent);
396	if(params->dumpDigest) {
397		dumpDigest(digest, digestLen);
398	}
399	return CSSM_OK;
400}
401
402/* Lots of data, CryptKit version */
403
404/* cryptkit final routines are not orthoganal, fix up here */
405static void ckSha1Final(
406	unsigned char *digest,
407	void *ctx)
408{
409	sha1GetDigest((sha1Obj)ctx, digest);
410}
411
412static void ckMD5Final(
413	unsigned char *digest,
414	void *ctx)
415{
416	MD5Final((struct MD5Context *)ctx, digest);
417}
418
419typedef void (*ckUpdateFcn)(void *digestCtx, const void *data, unsigned len);
420typedef void (*ckFinalFcn)(unsigned char *digest, void *digestCtx);
421
422static CSSM_RETURN hashDataRateCryptKit(
423	TestParams	*params)
424{
425	ckUpdateFcn		updatePtr = NULL;
426	ckFinalFcn		finalPtr = NULL;
427	initFcn			initPtr = NULL;
428	struct MD5Context	md5;
429	sha1Obj				sha;
430	void			*ctx;
431
432	unsigned 		loop;
433	unsigned		iloop;
434	CPUTime 		startTime;
435	double			timeSpent, timeSpentMs;
436	uint8			ptext[PTEXT_SIZE];
437	uint8			digest[MAX_DIGEST_SIZE];
438	unsigned		digestLen = 16;
439
440	/* we reuse this one inside the loop */
441	switch(params->algId) {
442		case CSSM_ALGID_SHA1:
443			sha = sha1Alloc();
444			ctx = sha;
445			initPtr = (initFcn)sha1Reinit;
446			updatePtr = (ckUpdateFcn)sha1AddData;
447			finalPtr = (ckFinalFcn)ckSha1Final;
448			digestLen = 20;
449			break;
450		case CSSM_ALGID_MD5:
451			ctx = &md5;
452			initPtr = (initFcn)MD5Init;
453			updatePtr = (ckUpdateFcn)MD5Update;
454			finalPtr = (ckFinalFcn)ckMD5Final;
455			break;
456		default:
457			printf("***Sorry, CryptKit can only do SHA1 and MD5.\n");
458			return 1;
459	}
460
461	/* random data, const thru the loops */
462	initPtext(ptext, PTEXT_SIZE);
463
464	/* start critical timing loop */
465	startTime = CPUTimeRead();
466	for(loop=0; loop<params->loops; loop++) {
467		initPtr(ctx);
468		for(iloop=0; iloop<INNER_LOOPS; iloop++) {
469			updatePtr(ctx, ptext, PTEXT_SIZE);
470		}
471		finalPtr(digest, ctx);
472	}
473	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
474	timeSpent = timeSpentMs / 1000.0;
475
476	float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
477	float totalBytes   = params->loops * bytesPerLoop;
478
479	/* careful, KByte = 1024, ms = 1/1000 */
480	printf("   Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
481		bytesPerLoop, params->loops,
482		timeSpentMs, timeSpentMs / (double)params->loops,
483		((float)totalBytes / 1024.0) / timeSpent);
484	if(params->dumpDigest) {
485		dumpDigest(digest, digestLen);
486	}
487	return CSSM_OK;
488}
489
490typedef CSSM_RETURN (*testRunFcn)(TestParams *testParams);
491
492/*
493 * Static declaration of a test
494 */
495typedef struct {
496	const char 			*testName;
497	unsigned			loops;
498	testRunFcn			run;
499	char				testSpec;		// for t=xxx cmd line opt
500} TestDefs;
501
502static TestDefs testDefs[] =
503{
504	{ 	"Digest context setup/teardown",
505		100000,
506		hashContext,
507		'c',
508	},
509	{ 	"Basic single block digest",
510		100000,
511		hashBasic,
512		'b',
513	},
514	{ 	"Large data digest",
515		1000,
516		hashDataRate,
517		'd',
518	},
519};
520
521static TestDefs testDefsOpenSSL[] =
522{
523	{ 	"Digest context setup/teardown",
524		100000,
525		NULL,			// not implemented
526		'c',
527	},
528	{ 	"Basic single block digest",
529		100000,
530		NULL,			// not implemented
531		'b',
532	},
533	{ 	"Large data digest, OpenSSL",
534		1000,
535		hashDataRateOpenssl,
536		'd',
537	},
538};
539
540static TestDefs testDefsCommonCrypto[] =
541{
542	{ 	"Digest context setup/teardown",
543		100000,
544		NULL,			// not implemented
545		'c',
546	},
547	{ 	"Basic single block digest",
548		100000,
549		NULL,			// not implemented
550		'b',
551	},
552	{ 	"Large data digest, CommonCrypto",
553		1000,
554		hashDataRateCommonCrypto,
555		'd',
556	},
557};
558
559static TestDefs testDefsCryptKit[] =
560{
561	{ 	"Digest context setup/teardown",
562		100000,
563		NULL,			// not implemented
564		'c',
565	},
566	{ 	"Basic single block digest",
567		100000,
568		NULL,			// not implemented
569		'b',
570	},
571	{ 	"Large data digest, CryptKit",
572		1000,
573		hashDataRateCryptKit,
574		'd',
575	},
576};
577
578
579static void algToAlgId(
580	HT_Alg			alg,
581	CSSM_ALGORITHMS	*algId,
582	const char		**algStr)
583{
584	switch(alg) {
585		case HA_MD5:
586			*algId = CSSM_ALGID_MD5;
587			*algStr = "MD5";
588			break;
589		case HA_SHA1:
590			*algId = CSSM_ALGID_SHA1;
591			*algStr = "SHA1";
592			break;
593		case HA_SHA224:
594			*algId = CSSM_ALGID_SHA224;
595			*algStr = "SHA224";
596			break;
597		case HA_SHA256:
598			*algId = CSSM_ALGID_SHA256;
599			*algStr = "SHA256";
600			break;
601		case HA_SHA384:
602			*algId = CSSM_ALGID_SHA384;
603			*algStr = "SHA384";
604			break;
605		case HA_SHA512:
606			*algId = CSSM_ALGID_SHA512;
607			*algStr = "SHA512";
608			break;
609		default:
610			printf("***algToAlgId screwup\n");
611			exit(1);
612	}
613}
614
615#define NUM_TESTS	(sizeof(testDefs) / sizeof(testDefs[0]))
616
617int main(int argc, char **argv)
618{
619	TestParams 		testParams;
620	TestDefs		*testDef;
621	TestDefs		*ourTestDefs = testDefs;
622	CSSM_RETURN		crtn;
623	int 			arg;
624	char			*argp;
625	unsigned		cmdLoops = 0;		// can be specified in cmd line
626										// if not, use TestDefs.loops
627	char			testSpec = '\0';	// allows specification of one test
628										// otherwise run all
629	HT_Alg			alg;
630	const char		*algStr;
631	int				firstAlg = FIRST_ALG;
632	int				lastAlg = LAST_ALG;
633
634	memset(&testParams, 0, sizeof(testParams));
635
636	for(arg=1; arg<argc; arg++) {
637		argp = argv[arg];
638		switch(argp[0]) {
639			case 't':
640				testSpec = argp[2];
641				break;
642			case 'l':
643				cmdLoops = atoi(&argp[2]);
644				break;
645			case 'a':
646				if(argp[1] == '\0') {
647					usage(argv);
648				}
649				switch(argp[2]) {
650					case 'm':
651						firstAlg = lastAlg = HA_MD5;
652						break;
653					case 's':
654						firstAlg = lastAlg = HA_SHA1;
655						break;
656					case '4':
657						firstAlg = lastAlg = HA_SHA224;
658						break;
659					case '2':
660						firstAlg = lastAlg = HA_SHA256;
661						break;
662					case '3':
663						firstAlg = lastAlg = HA_SHA384;
664						break;
665					case '5':
666						firstAlg = lastAlg = HA_SHA512;
667						break;
668					default:
669						usage(argv);
670				}
671				break;
672			case 'o':
673				ourTestDefs = testDefsOpenSSL;
674				break;
675			case 'c':
676				ourTestDefs = testDefsCommonCrypto;
677				break;
678			case 'k':
679				ourTestDefs = testDefsCryptKit;
680				break;
681			case 'v':
682				testParams.dumpDigest = true;
683				break;
684			default:
685				usage(argv);
686		}
687	}
688
689	testParams.cspHand = cspStartup();
690	if(testParams.cspHand == 0) {
691		printf("***Error attaching to CSP. Aborting.\n");
692		exit(1);
693	}
694
695	for(unsigned testNum=0; testNum<NUM_TESTS; testNum++) {
696		testDef = &ourTestDefs[testNum];
697
698		if(testSpec && (testDef->testSpec != testSpec)) {
699			continue;
700		}
701		if(testDef->run == NULL) {
702			continue;
703		}
704		printf("%s:\n", testDef->testName);
705		if(cmdLoops) {
706			/* user specified */
707			testParams.loops = cmdLoops;
708		}
709		else {
710			/* default */
711			testParams.loops = testDef->loops;
712		}
713		for(alg=firstAlg; alg<=lastAlg; alg++) {
714			algToAlgId(alg, &testParams.algId, &algStr);
715			printf("   === %s ===\n", algStr);
716			crtn = testDef->run(&testParams);
717			if(crtn) {
718				exit(1);
719			}
720		}
721	}
722	return 0;
723}
724