home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #19 / NN_1992_19.iso / spool / sci / crypt / 3101 < prev    next >
Encoding:
Text File  |  1992-09-03  |  18.5 KB  |  516 lines

  1. Path: sparky!uunet!usc!cs.utexas.edu!wupost!waikato.ac.nz!aukuni.ac.nz!cs18.cs.aukuni.ac.nz!pgut1
  2. Newsgroups: sci.crypt
  3. Subject: SHS/SHA source code + random thoughts
  4. Message-ID: <1992Sep4.060230.28313@cs.aukuni.ac.nz>
  5. From: Peter Gutmann (pgut1@cs.aukuni.ac.nz)
  6. Date: Fri, 4 Sep 1992 06:02:30 GMT
  7. Sender: pgut1@cs.aukuni.ac.nz (PeterClaus          Gutmann        )
  8. Organization: HPACK Conspiracy Secret Laboratory
  9. Keywords: SHS SHA message digest
  10. Summary: Source code for SHS/SHA message digest algorithm + comparison with MD5
  11. Lines: 503
  12.  
  13.   The following is a C implementation of the NIST SHA (Secure Hash Algorithm) -
  14. this is sometimes also referred to as SHS (Secure Hash Standard), but I've used
  15. SHA for now since the standard hasn't been adopted yet.  It's called SHS and
  16. SHA rather interchangeably just to confuse people, I've called it SHA in the
  17. text but SHS in the code in anticipation of it becoming a standard.  Once it's
  18. adopted, you can feed the text through sed and change all the names.  I'll
  19. assume everyone has a copy of the SHS document and won't bother including it in
  20. this posting (it's rather long).
  21.  
  22.  
  23. The SHS/SHA Code
  24. ================
  25.  
  26.   It's a fairly straightforward implementation which has been tested under
  27. MSDOS and OS/2 on a PClone, and Unix on a DECstation.  Some of the
  28. optimizations used are mentioned in the code.  One of the results of these
  29. optimizations is that the code isn't endianness-independant, meaning that on
  30. little-endian machines a byteReverse() function has to be used for
  31. endianness-reversal.  This entails defining LITTLE_ENDIAN on a little-endian
  32. system (it's currently defined by default in SHS.H).  Being able to assume a
  33. given data endianness makes getting data to the SHA transform() routine a lot
  34. faster.
  35.  
  36.  
  37. SHA Speed
  38. =========
  39.  
  40.   I ran the SHA code against the distributed version of MD5 (which is
  41. significantly less optimised than the SHA code).  The results were as follows:
  42.  
  43.           25 MHz PClone     DECstation 2100     DECstation 5000
  44.  
  45.     SHA         31 K/sec           120 K/sec           208 K/sec
  46.     MD5         55 K/sec           169 K/sec           278 K/sec
  47.  
  48. This comparison isn't 100% fair since the standard MD5 distribution is a
  49. somewhat pessimal implementation (in fact an optimised PC version runs around 5
  50. times faster).  An implementation of MD5 optimised to the level of SHA runs
  51. around 2 times faster.
  52.  
  53. Therefore with similar levels of optimisation it appears MD5 is around three
  54. times as fast on the PClone as SHA.  Even the pessimal MD5 on the DECstations
  55. is around a third as fast again as SHA, and would probably also be 2 or more
  56. times as fast if optimized (I used the standard MD5 distribution rather than
  57. an optimized custom one to allow others to verify the results).
  58.  
  59.  
  60. SHA's Awkward Block Size (Some Flamage)
  61. ========================
  62.  
  63. Will SHA be weakened by taking out h4 and E and reducing the number of rounds
  64. to create a 128-bit MD algorithm?  This seems a lot nicer than the current one
  65. since it's now a power-of-two size which fits in a lot better with most current
  66. software.  Removing h4 and E and reducing the total number of rounds by 16
  67. doesn't seem to weaken it any, and IMHO the decision to force SHA to fit an
  68. awkward DSS data size wasn't such a hot idea, especially if it's to be used
  69. with non-DSS code or if q is ever changed.
  70.  
  71.  
  72. SHA vs MD5
  73. ==========
  74.  
  75. When implementing SHA I noticed how very similar it was to MD4.  The main
  76. changes were the addition of an the 'expand' transformation (Step B), the
  77. addition of the previous round's output into the next round for a faster
  78. avalance effect, and the increasing of the whole transformation to fit the DSS
  79. block size.  SHA is very much an MD4-variant rather than a redesign like MD5.
  80.  
  81. The design decisions behind MD5 were given in the MD5 document, the design
  82. for SHA is never gone into in the SHS/SHA document (mind you it's pretty
  83. obvious what's going on - everything except how the Mysterious Constants
  84. were chosen can be seen at a glance).  Presumably some of the changes made
  85. were to avoid the known attacks for MD4, but again no design details are
  86. given.  Anyway, using what I had available I took the points raised in the
  87. MD5 design and compared them with what SHA did:
  88.  
  89. - A fourth round has been added.
  90.  
  91.   SHA does this too.  However in SHA the fourth round uses the same f-function
  92.   as the second round (not obvious whether this is a problem or not, I'll have
  93.   to look at it a bit more).
  94.  
  95. - Each step now has a unique additive constant.
  96.  
  97.   SHA keeps the MD4 scheme where it reuses the constants for each group of
  98.   rounds (in this case for 20 rounds at a time).  Actually MD4 only has the
  99.   additive constants in the last two rounds, not for all 4, but the principal
  100.   is the same - the constants are reused many times.
  101.  
  102. - The function g in round 2 was changed from ( XY v XZ v YZ ) to
  103.   ( XZ v Y not( Z ) ) to make g less symmetric.
  104.  
  105.   SHA uses the MD4 version ( XY v XZ v YZ ) (SHA calls it f2 rather than g).
  106.  
  107. - Each step now adds in the result of the previous step.  This promotes a faster
  108.   "avalanche effect".
  109.  
  110.   This change has been made in SHA as well.
  111.  
  112. - The order in which input words are accessed in rounds 2 and 3 is changed, to
  113.   make these patterns less like each other.
  114.       
  115.   SHA always access the words the same way, like MD4.
  116.  
  117. - The shift amounts in each round have been approximately optimized, to yield a
  118.   faster "avalanche effect". The shifts in different rounds are distinct.
  119.  
  120.   SHA uses a constant shift amount in each round.  This shift amount is
  121.   relatively prime to the word size (as in MD4).
  122.  
  123. If you take SHA and remove h4, E, and a few rounds, the result is (basically)
  124. MD4 with one more round, the addition of the output of step n-1 to step n
  125. (giving a faster avalanche effect), and the addition of the initial "expand"
  126. transformation.  This initial transformation is important, since it spreads the
  127. input data bits out over an area four times as large as the original, and then
  128. mixes in the expanded version of the data in each round.  This means that
  129. instead of reusing the input data in each group of rounds, SHA uses different
  130. permutations of the input data in each group of rounds.  This is definitely A
  131. Good Thing.
  132.  
  133. This leads to the following situation:  
  134.  
  135.   SHA = MD4 + 'expand' transformation + extra round + better-avalanche
  136.  
  137.   MD5 = MD4 + improved bit-bashing + extra round + better-avalanche
  138.  
  139. Which is stronger, MD5 with its improved bit-bashing or SHA with it's 'expand'
  140. transformation?  (Ain't no way I'm going to answer this one :-).
  141.  
  142. One point is that the only "extra" in SHA which MD5 doesn't have, namely the
  143. 'expand' transformation, can be easily added to MD5, but that the MD5
  144. improvements can't be added to SHA without redesigning the whole algorithm.
  145. Thus the paranoid types can hedge their bets by adding 'expand' to MD5, giving
  146. them the best of both worlds (its very easy to simply change MD5 to have the
  147. 'expand' transformation - maybe this is an MD6 in the making?).
  148.               
  149.  
  150. Conclusion
  151. ==========
  152.  
  153.   This positing is beginning to sound as if I'm the official net.apologist for
  154. MD5 :-).  Maybe I'm being a bit harsh on SHA, but I think someone should point
  155. out that it may not be the be-all and end-all of message digest algorithms.  I
  156. welcome any email on the subject, or post your flames here....
  157.  
  158. Peter.
  159.  
  160. ------------------------------- Chainsaw here ---------------------------------
  161. #include <stdio.h>
  162. #include <stdlib.h>
  163. #include <time.h>
  164.  
  165. /* --------------------------------- SHS.H ------------------------------- */
  166.  
  167. /* NIST proposed Secure Hash Standard.
  168.  
  169.    Written 2 September 1992, Peter C. Gutmann.
  170.    This implementation placed in the public domain.
  171.  
  172.    Comments to pgut1@cs.aukuni.ac.nz */
  173.  
  174. /* Useful defines/typedefs */
  175.  
  176. typedef unsigned char   BYTE;
  177. typedef unsigned long   LONG;
  178.  
  179. /* The SHS block size and message digest sizes, in bytes */
  180.  
  181. #define SHS_BLOCKSIZE   64
  182. #define SHS_DIGESTSIZE  20
  183.  
  184. /* The structure for storing SHS info */
  185.  
  186. typedef struct {
  187.            LONG digest[ 5 ];            /* Message digest */
  188.            LONG countLo, countHi;       /* 64-bit bit count */
  189.            LONG data[ 16 ];             /* SHS data buffer */
  190.            } SHS_INFO;
  191.  
  192. /* Whether the machine is little-endian or not */
  193.  
  194. #define LITTLE_ENDIAN
  195.  
  196. /* --------------------------------- SHS.C ------------------------------- */
  197.  
  198. /* NIST proposed Secure Hash Standard.
  199.  
  200.    Written 2 September 1992, Peter C. Gutmann.
  201.    This implementation placed in the public domain.
  202.  
  203.    Comments to pgut1@cs.aukuni.ac.nz */
  204.  
  205. #include <string.h>
  206.  
  207. /* The SHS f()-functions */
  208.  
  209. #define f1(x,y,z)   ( ( x & y ) | ( ~x & z ) )              /* Rounds  0-19 */
  210. #define f2(x,y,z)   ( x ^ y ^ z )                           /* Rounds 20-39 */
  211. #define f3(x,y,z)   ( ( x & y ) | ( x & z ) | ( y & z ) )   /* Rounds 40-59 */
  212. #define f4(x,y,z)   ( x ^ y ^ z )                           /* Rounds 60-79 */
  213.  
  214. /* The SHS Mysterious Constants */
  215.  
  216. #define K1  0x5A827999L     /* Rounds  0-19 */
  217. #define K2  0x6ED9EBA1L     /* Rounds 20-39 */
  218. #define K3  0x8F1BBCDCL     /* Rounds 40-59 */
  219. #define K4  0xCA62C1D6L     /* Rounds 60-79 */
  220.  
  221. /* SHS initial values */
  222.  
  223. #define h0init  0x67452301L
  224. #define h1init  0xEFCDAB89L
  225. #define h2init  0x98BADCFEL
  226. #define h3init  0x10325476L
  227. #define h4init  0xC3D2E1F0L
  228.  
  229. /* 32-bit rotate - kludged with shifts */
  230.  
  231. #define S(n,X)  ( ( X << n ) | ( X >> ( 32 - n ) ) )
  232.  
  233. /* The initial expanding function */
  234.  
  235. #define expand(count)   W[ count ] = W[ count - 3 ] ^ W[ count - 8 ] ^ W[ count - 14 ] ^ W[ count - 16 ]
  236.  
  237. /* The four SHS sub-rounds */
  238.  
  239. #define subRound1(count)    \
  240.     { \
  241.     temp = S( 5, A ) + f1( B, C, D ) + E + W[ count ] + K1; \
  242.     E = D; \
  243.     D = C; \
  244.     C = S( 30, B ); \
  245.     B = A; \
  246.     A = temp; \
  247.     }
  248.  
  249. #define subRound2(count)    \
  250.     { \
  251.     temp = S( 5, A ) + f2( B, C, D ) + E + W[ count ] + K2; \
  252.     E = D; \
  253.     D = C; \
  254.     C = S( 30, B ); \
  255.     B = A; \
  256.     A = temp; \
  257.     }
  258.  
  259. #define subRound3(count)    \
  260.     { \
  261.     temp = S( 5, A ) + f3( B, C, D ) + E + W[ count ] + K3; \
  262.     E = D; \
  263.     D = C; \
  264.     C = S( 30, B ); \
  265.     B = A; \
  266.     A = temp; \
  267.     }
  268.  
  269. #define subRound4(count)    \
  270.     { \
  271.     temp = S( 5, A ) + f4( B, C, D ) + E + W[ count ] + K4; \
  272.     E = D; \
  273.     D = C; \
  274.     C = S( 30, B ); \
  275.     B = A; \
  276.     A = temp; \
  277.     }
  278.  
  279. /* The two buffers of 5 32-bit words */
  280.  
  281. LONG h0, h1, h2, h3, h4;
  282. LONG A, B, C, D, E;
  283.  
  284. /* Initialize the SHS values */
  285.  
  286. void shsInit( SHS_INFO *shsInfo )
  287.     {
  288.     /* Set the h-vars to their initial values */
  289.     shsInfo->digest[ 0 ] = h0init;
  290.     shsInfo->digest[ 1 ] = h1init;
  291.     shsInfo->digest[ 2 ] = h2init;
  292.     shsInfo->digest[ 3 ] = h3init;
  293.     shsInfo->digest[ 4 ] = h4init;
  294.  
  295.     /* Initialise bit count */
  296.     shsInfo->countLo = shsInfo->countHi = 0L;
  297.     }
  298.  
  299. /* Perform the SHS transformation.  Note that this code, like MD5, seems to
  300.    break some optimizing compilers - it may be necessary to split it into
  301.    sections, eg based on the four subrounds */
  302.  
  303. void shsTransform( SHS_INFO *shsInfo )
  304.     {
  305.     LONG W[ 80 ], temp;
  306.     int i;
  307.  
  308.     /* Step A.  Copy the data buffer into the local work buffer */
  309.     for( i = 0; i < 16; i++ )
  310.     W[ i ] = shsInfo->data[ i ];
  311.  
  312.     /* Step B.  Expand the 16 words into 64 temporary data words */
  313.     expand( 16 ); expand( 17 ); expand( 18 ); expand( 19 ); expand( 20 );
  314.     expand( 21 ); expand( 22 ); expand( 23 ); expand( 24 ); expand( 25 );
  315.     expand( 26 ); expand( 27 ); expand( 28 ); expand( 29 ); expand( 30 );
  316.     expand( 31 ); expand( 32 ); expand( 33 ); expand( 34 ); expand( 35 );
  317.     expand( 36 ); expand( 37 ); expand( 38 ); expand( 39 ); expand( 40 );
  318.     expand( 41 ); expand( 42 ); expand( 43 ); expand( 44 ); expand( 45 );
  319.     expand( 46 ); expand( 47 ); expand( 48 ); expand( 49 ); expand( 50 );
  320.     expand( 51 ); expand( 52 ); expand( 53 ); expand( 54 ); expand( 55 );
  321.     expand( 56 ); expand( 57 ); expand( 58 ); expand( 59 ); expand( 60 );
  322.     expand( 61 ); expand( 62 ); expand( 63 ); expand( 64 ); expand( 65 );
  323.     expand( 66 ); expand( 67 ); expand( 68 ); expand( 69 ); expand( 70 );
  324.     expand( 71 ); expand( 72 ); expand( 73 ); expand( 74 ); expand( 75 );
  325.     expand( 76 ); expand( 77 ); expand( 78 ); expand( 79 );
  326.  
  327.     /* Step C.  Set up first buffer */
  328.     A = shsInfo->digest[ 0 ];
  329.     B = shsInfo->digest[ 1 ];
  330.     C = shsInfo->digest[ 2 ];
  331.     D = shsInfo->digest[ 3 ];
  332.     E = shsInfo->digest[ 4 ];
  333.  
  334.     /* Step D.  Serious mangling, divided into four sub-rounds */
  335.     subRound1( 0 ); subRound1( 1 ); subRound1( 2 ); subRound1( 3 );
  336.     subRound1( 4 ); subRound1( 5 ); subRound1( 6 ); subRound1( 7 );
  337.     subRound1( 8 ); subRound1( 9 ); subRound1( 10 ); subRound1( 11 );
  338.     subRound1( 12 ); subRound1( 13 ); subRound1( 14 ); subRound1( 15 );
  339.     subRound1( 16 ); subRound1( 17 ); subRound1( 18 ); subRound1( 19 );
  340.     subRound2( 20 ); subRound2( 21 ); subRound2( 22 ); subRound2( 23 );
  341.     subRound2( 24 ); subRound2( 25 ); subRound2( 26 ); subRound2( 27 );
  342.     subRound2( 28 ); subRound2( 29 ); subRound2( 30 ); subRound2( 31 );
  343.     subRound2( 32 ); subRound2( 33 ); subRound2( 34 ); subRound2( 35 );
  344.     subRound2( 36 ); subRound2( 37 ); subRound2( 38 ); subRound2( 39 );
  345.     subRound3( 40 ); subRound3( 41 ); subRound3( 42 ); subRound3( 43 );
  346.     subRound3( 44 ); subRound3( 45 ); subRound3( 46 ); subRound3( 47 );
  347.     subRound3( 48 ); subRound3( 49 ); subRound3( 50 ); subRound3( 51 );
  348.     subRound3( 52 ); subRound3( 53 ); subRound3( 54 ); subRound3( 55 );
  349.     subRound3( 56 ); subRound3( 57 ); subRound3( 58 ); subRound3( 59 );
  350.     subRound4( 60 ); subRound4( 61 ); subRound4( 62 ); subRound4( 63 );
  351.     subRound4( 64 ); subRound4( 65 ); subRound4( 66 ); subRound4( 67 );
  352.     subRound4( 68 ); subRound4( 69 ); subRound4( 70 ); subRound4( 71 );
  353.     subRound4( 72 ); subRound4( 73 ); subRound4( 74 ); subRound4( 75 );
  354.     subRound4( 76 ); subRound4( 77 ); subRound4( 78 ); subRound4( 79 );
  355.  
  356.     /* Step E.  Build message digest */
  357.     shsInfo->digest[ 0 ] += A;
  358.     shsInfo->digest[ 1 ] += B;
  359.     shsInfo->digest[ 2 ] += C;
  360.     shsInfo->digest[ 3 ] += D;
  361.     shsInfo->digest[ 4 ] += E;
  362.     }
  363.  
  364. #ifdef LITTLE_ENDIAN
  365.  
  366. /* When run on a little-endian CPU we need to perform byte reversal on an
  367.    array of longwords.  It is possible to make the code endianness-
  368.    independant by fiddling around with data at the byte level, but this
  369.    makes for very slow code, so we rely on the user to sort out endianness
  370.    at compile time */
  371.  
  372. static void byteReverse( LONG *buffer, int byteCount )
  373.     {
  374.     LONG value;
  375.     int count;
  376.  
  377.     byteCount /= sizeof( LONG );
  378.     for( count = 0; count < byteCount; count++ )
  379.     {
  380.     value = ( buffer[ count ] << 16 ) | ( buffer[ count ] >> 16 );
  381.     buffer[ count ] = ( ( value & 0xFF00FF00L ) >> 8 ) | ( ( value & 0x00FF00FFL ) << 8 );
  382.     }
  383.     }
  384. #endif /* LITTLE_ENDIAN */
  385.  
  386. /* Update SHS for a block of data.  This code assumes that the buffer size
  387.    is a multiple of SHS_BLOCKSIZE bytes long, which makes the code a lot
  388.    more efficient since it does away with the need to handle partial blocks
  389.    between calls to shsUpdate() */
  390.  
  391. void shsUpdate( SHS_INFO *shsInfo, BYTE *buffer, int count )
  392.     {
  393.     /* Update bitcount */
  394.     if( ( shsInfo->countLo + ( ( LONG ) count << 3 ) ) < shsInfo->countLo )
  395.     shsInfo->countHi++; /* Carry from low to high bitCount */
  396.     shsInfo->countLo += ( ( LONG ) count << 3 );
  397.     shsInfo->countHi += ( ( LONG ) count >> 29 );
  398.  
  399.     /* Process data in SHS_BLOCKSIZE chunks */
  400.     while( count >= SHS_BLOCKSIZE )
  401.     {
  402.     memcpy( shsInfo->data, buffer, SHS_BLOCKSIZE );
  403. #ifdef LITTLE_ENDIAN
  404.     byteReverse( shsInfo->data, SHS_BLOCKSIZE );
  405. #endif /* LITTLE_ENDIAN */
  406.     shsTransform( shsInfo );
  407.     buffer += SHS_BLOCKSIZE;
  408.     count -= SHS_BLOCKSIZE;
  409.     }
  410.  
  411.     /* Handle any remaining bytes of data.  This should only happen once
  412.        on the final lot of data */
  413.     memcpy( shsInfo->data, buffer, count );
  414.     }
  415.  
  416. void shsFinal( SHS_INFO *shsInfo )
  417.     {
  418.     int count;
  419.     LONG lowBitcount = shsInfo->countLo, highBitcount = shsInfo->countHi;
  420.  
  421.     /* Compute number of bytes mod 64 */
  422.     count = ( int ) ( ( shsInfo->countLo >> 3 ) & 0x3F );
  423.  
  424.     /* Set the first char of padding to 0x80.  This is safe since there is
  425.        always at least one byte free */
  426.     ( ( BYTE * ) shsInfo->data )[ count++ ] = 0x80;
  427.  
  428.     /* Pad out to 56 mod 64 */
  429.     if( count > 56 )
  430.     {
  431.     /* Two lots of padding:  Pad the first block to 64 bytes */
  432.     memset( ( BYTE * ) &shsInfo->data + count, 0, 64 - count );
  433. #ifdef LITTLE_ENDIAN
  434.     byteReverse( shsInfo->data, SHS_BLOCKSIZE );
  435. #endif /* LITTLE_ENDIAN */
  436.     shsTransform( shsInfo );
  437.  
  438.     /* Now fill the next block with 56 bytes */
  439.     memset( &shsInfo->data, 0, 56 );
  440.     }
  441.     else
  442.     /* Pad block to 56 bytes */
  443.     memset( ( BYTE * ) &shsInfo->data + count, 0, 56 - count );
  444. #ifdef LITTLE_ENDIAN
  445.     byteReverse( shsInfo->data, SHS_BLOCKSIZE );
  446. #endif /* LITTLE_ENDIAN */
  447.  
  448.     /* Append length in bits and transform */
  449.     shsInfo->data[ 14 ] = highBitcount;
  450.     shsInfo->data[ 15 ] = lowBitcount;
  451.  
  452.     shsTransform( shsInfo );
  453. #ifdef LITTLE_ENDIAN
  454.     byteReverse( shsInfo->data, SHS_DIGESTSIZE );
  455. #endif /* LITTLE_ENDIAN */
  456.     }
  457.  
  458. /* ----------------------------- SHS Test code --------------------------- */
  459.  
  460. /* Size of buffer for SHS speed test data */
  461.  
  462. #define TEST_BLOCK_SIZE     ( SHS_DIGESTSIZE * 100 )
  463.  
  464. /* Number of bytes of test data to process */
  465.  
  466. #define TEST_BYTES          10000000L
  467. #define TEST_BLOCKS         ( TEST_BYTES / TEST_BLOCK_SIZE )
  468.  
  469. void main( void )
  470.     {
  471.     SHS_INFO shsInfo;
  472.     time_t endTime, startTime;
  473.     BYTE data[ TEST_BLOCK_SIZE ];
  474.     long i;
  475.  
  476.     /* Test output data (this is the only test data given in the SHS
  477.        document, but chances are if it works for this it'll work for
  478.        anything) */
  479.     shsInit( &shsInfo );
  480.     shsUpdate( &shsInfo, ( BYTE * ) "abc", 3 );
  481.     shsFinal( &shsInfo );
  482.     if( shsInfo.digest[ 0 ] != 0x0164B8A9L || \
  483.     shsInfo.digest[ 1 ] != 0x14CD2A5EL || \
  484.     shsInfo.digest[ 2 ] != 0x74C4F7FFL || \
  485.     shsInfo.digest[ 3 ] != 0x082C4D97L || \
  486.     shsInfo.digest[ 4 ] != 0xF1EDF880L )
  487.     {
  488.     puts( "Error in SHS implementation" );
  489.     exit( -1 );
  490.     }
  491.  
  492.     /* Now perform time trial, generating MD for 10MB of data.  First,
  493.        initialize the test data */
  494.     memset( data, 0, TEST_BLOCK_SIZE );
  495.  
  496.     /* Get start time */
  497.     printf( "SHS time trial.  Processing %ld characters...\n", TEST_BYTES );
  498.     time( &startTime );
  499.  
  500.     /* Calculate SHS message digest in TEST_BLOCK_SIZE byte blocks */
  501.     shsInit( &shsInfo );
  502.     for( i = TEST_BLOCKS; i > 0; i-- )
  503.     shsUpdate( &shsInfo, data, TEST_BLOCK_SIZE );
  504.     shsFinal( &shsInfo );
  505.  
  506.     /* Get finish time and time difference */
  507.     time( &endTime );
  508.     printf( "Seconds to process test input: %ld\n", endTime - startTime );
  509.     printf( "Characters processed per second: %ld\n", TEST_BYTES / ( endTime - startTime ) );
  510.     }
  511. --
  512.      pgut1@cs.aukuni.ac.nz || peterg@kcbbs.gen.nz || peter@nacjack.gen.nz
  513.        or sling a bottle in the ocean, it'd be about as reliable as our
  514.                    email currently is.
  515.  
  516.