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