Performance Testing @ the Frontline

A hidden world where small things make a big difference

SHA-1 hash for LoadRunner

Posted by Kim on Tuesday, March 2, 2010

One of my colleagues at work was testing a solution that obfuscated passwords using the SHA1 algorithm, and was looking for a suitable solution to replicate this with LoadRunner. As I’ve used SHA1 before for other things, I promised to look around and see if I could help out.

As it turns out a suitable source was found and I made the necessary changes to the code to make it work in LoadRunner. A big thank you to Paul E. Jones for providing a free SHA1 implementation.

I created an include file names “lr_sha1.c” that can easily be included in any C based LoadRunner script or VUser type.

Example of usage (vuser_init.c):

#include "lr_sha1.c"
vuser_init()
{
	//
	// HASH the string "The quick brown fox jumps over the lazy dog",
	// The result should be "2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12"
	//
	sha1_hash("The quick brown fox jumps over the lazy dog", "_sha1");

	lr_output_message( "SHA1 Hash: %s", lr_eval_string("{_sha1}") );

	return 0;
}

And here is the lr_sha1.c file:

/*
 *****************************************************************************
 *
 *  March 2010
 *
 *    Small changes by Kim Sandell to make the source work in LoadRunner
 *    - Changed "const unsigned char" to "const char" in function params
 *    - Combined sha1.h and sha1.c into one file (for ease of use in LR)
 *    - Added sha1_hash() function to ease use in LR
 *    - Included Paul's license in comments
 *
 *****************************************************************************
 *
 *  Freeware Public License (FPL)
 *
 *  This software is licensed as "freeware."  Permission to distribute
 *  this software in source and binary forms, including incorporation
 *  into other products, is hereby granted without a fee.  THIS SOFTWARE
 *  IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESSED OR IMPLIED WARRANTIES,
 *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 *  AND FITNESS FOR A PARTICULAR PURPOSE.  THE AUTHOR SHALL NOT BE HELD
 *  LIABLE FOR ANY DAMAGES RESULTING FROM THE USE OF THIS SOFTWARE, EITHER
 *  DIRECTLY OR INDIRECTLY, INCLUDING, BUT NOT LIMITED TO, LOSS OF DATA
 *  OR DATA BEING RENDERED INACCURATE.
 *
 *****************************************************************************
 *
 *  sha1.h
 *
 *  Copyright (C) 1998, 2009
 *  Paul E. Jones <paulej@packetizer.com>
 *  All Rights Reserved
 *
 *****************************************************************************
 *  $Id: sha1.h 12 2009-06-22 19:34:25Z paulej $
 *****************************************************************************
 *
 *  Description:
 *      This class implements the Secure Hashing Standard as defined
 *      in FIPS PUB 180-1 published April 17, 1995.
 *
 *      Many of the variable names in the SHA1Context, especially the
 *      single character names, were used because those were the names
 *      used in the publication.
 *
*/

#ifndef _SHA1_H_
#define _SHA1_H_

/*
 *  This structure will hold context information for the hashing
 *  operation
 */
typedef struct SHA1Context
{
 unsigned Message_Digest[5];       /* Message Digest (output)          */
 unsigned Length_Low;              /* Message length in bits           */
 unsigned Length_High;             /* Message length in bits           */
 unsigned char Message_Block[64];  /* 512-bit message blocks      */
 int Message_Block_Index;          /* Index into message block array   */
 int Computed;                     /* Is the digest computed?          */
 int Corrupted;                    /* Is the message digest corruped?  */
} SHA1Context;

/*
 *  Function Prototypes
 */
void SHA1Reset(SHA1Context *);
int SHA1Result(SHA1Context *);
void SHA1Input( SHA1Context *,
 const char *,
 unsigned);
#endif

/*
 *****************************************************************************
 *
 *  sha1.c
 *
 *  Copyright (C) 1998, 2009
 *  Paul E. Jones <paulej@packetizer.com>
 *  All Rights Reserved
 *
 *****************************************************************************
 *  $Id: sha1.c 12 2009-06-22 19:34:25Z paulej $
 *****************************************************************************
 *
 *  Description:
 *      This file implements the Secure Hashing Standard as defined
 *      in FIPS PUB 180-1 published April 17, 1995.
 *
 *      The Secure Hashing Standard, which uses the Secure Hashing
 *      Algorithm (SHA), produces a 160-bit message digest for a
 *      given data stream.  In theory, it is highly improbable that
 *      two messages will produce the same message digest.  Therefore,
 *      this algorithm can serve as a means of providing a "fingerprint"
 *      for a message.
 *
 *  Portability Issues:
 *      SHA-1 is defined in terms of 32-bit "words".  This code was
 *      written with the expectation that the processor has at least
 *      a 32-bit machine word size.  If the machine word size is larger,
 *      the code should still function properly.  One caveat to that
 *      is that the input functions taking characters and character
 *      arrays assume that only 8 bits of information are stored in each
 *      character.
 *
 *  Caveats:
 *      SHA-1 is designed to work with messages less than 2^64 bits
 *      long. Although SHA-1 allows a message digest to be generated for
 *      messages of any number of bits less than 2^64, this
 *      implementation only works with messages with a length that is a
 *      multiple of the size of an 8-bit character.
 *
 *****************************************************************************
 */

/*
 *  Define the circular shift macro
 */
#define SHA1CircularShift(bits,word) \
 ((((word) << (bits)) & 0xFFFFFFFF) | \
 ((word) >> (32-(bits))))

/* Function prototypes */
void SHA1ProcessMessageBlock(SHA1Context *);
void SHA1PadMessage(SHA1Context *);

/*
 *  SHA1Reset
 *
 *  Description:
 *      This function will initialize the SHA1Context in preparation
 *      for computing a new message digest.
 *
 *  Parameters:
 *      context: [in/out]
 *          The context to reset.
 *
 *  Returns:
 *      Nothing.
 *
 *  Comments:
 *
 */
void SHA1Reset(SHA1Context *context)
{
 context->Length_Low             = 0;
 context->Length_High            = 0;
 context->Message_Block_Index    = 0;

 context->Message_Digest[0]      = 0x67452301;
 context->Message_Digest[1]      = 0xEFCDAB89;
 context->Message_Digest[2]      = 0x98BADCFE;
 context->Message_Digest[3]      = 0x10325476;
 context->Message_Digest[4]      = 0xC3D2E1F0;

 context->Computed   = 0;
 context->Corrupted  = 0;
}

/*
 *  SHA1Result
 *
 *  Description:
 *      This function will return the 160-bit message digest into the
 *      Message_Digest array within the SHA1Context provided
 *
 *  Parameters:
 *      context: [in/out]
 *          The context to use to calculate the SHA-1 hash.
 *
 *  Returns:
 *      1 if successful, 0 if it failed.
 *
 *  Comments:
 *
 */
int SHA1Result(SHA1Context *context)
{

 if (context->Corrupted)
 {
 return 0;
 }

 if (!context->Computed)
 {
 SHA1PadMessage(context);
 context->Computed = 1;
 }

 return 1;
}

/*
 *  SHA1Input
 *
 *  Description:
 *      This function accepts an array of octets as the next portion of
 *      the message.
 *
 *  Parameters:
 *      context: [in/out]
 *          The SHA-1 context to update
 *      message_array: [in]
 *          An array of characters representing the next portion of the
 *          message.
 *      length: [in]
 *          The length of the message in message_array
 *
 *  Returns:
 *      Nothing.
 *
 *  Comments:
 *
 */
void SHA1Input(     SHA1Context  *context,
 const char   *message_array,
 unsigned     length)
{
 if (!length)
 {
 return;
 }

 if (context->Computed || context->Corrupted)
 {
 context->Corrupted = 1;
 return;
 }

 while(length-- && !context->Corrupted)
 {
 context->Message_Block[context->Message_Block_Index++] =
 (*message_array & 0xFF);

 context->Length_Low += 8;
 /* Force it to 32 bits */
 context->Length_Low &= 0xFFFFFFFF;
 if (context->Length_Low == 0)
 {
 context->Length_High++;
 /* Force it to 32 bits */
 context->Length_High &= 0xFFFFFFFF;
 if (context->Length_High == 0)
 {
 /* Message is too long */
 context->Corrupted = 1;
 }
 }

 if (context->Message_Block_Index == 64)
 {
 SHA1ProcessMessageBlock(context);
 }

 message_array++;
 }
}

/*
 *  SHA1ProcessMessageBlock
 *
 *  Description:
 *      This function will process the next 512 bits of the message
 *      stored in the Message_Block array.
 *
 *  Parameters:
 *      None.
 *
 *  Returns:
 *      Nothing.
 *
 *  Comments:
 *      Many of the variable names in the SHAContext, especially the
 *      single character names, were used because those were the names
 *      used in the publication.
 *
 *
 */
void SHA1ProcessMessageBlock(SHA1Context *context)
{
 const unsigned K[] =            /* Constants defined in SHA-1   */
 {
 0x5A827999,
 0x6ED9EBA1,
 0x8F1BBCDC,
 0xCA62C1D6
 };
 int         t;                  /* Loop counter                 */
 unsigned    temp;               /* Temporary word value         */
 unsigned    W[80];              /* Word sequence                */
 unsigned    A, B, C, D, E;      /* Word buffers                 */

 /*
 *  Initialize the first 16 words in the array W
 */
 for(t = 0; t < 16; t++)
 {
 W[t] = ((unsigned) context->Message_Block[t * 4]) << 24;
 W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16;
 W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8;
 W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]);
 }

 for(t = 16; t < 80; t++)
 {
 W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
 }

 A = context->Message_Digest[0];
 B = context->Message_Digest[1];
 C = context->Message_Digest[2];
 D = context->Message_Digest[3];
 E = context->Message_Digest[4];

 for(t = 0; t < 20; t++)
 {
 temp =  SHA1CircularShift(5,A) +
 ((B & C) | ((~B) & D)) + E + W[t] + K[0];
 temp &= 0xFFFFFFFF;
 E = D;
 D = C;
 C = SHA1CircularShift(30,B);
 B = A;
 A = temp;
 }

 for(t = 20; t < 40; t++)
 {
 temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
 temp &= 0xFFFFFFFF;
 E = D;
 D = C;
 C = SHA1CircularShift(30,B);
 B = A;
 A = temp;
 }

 for(t = 40; t < 60; t++)
 {
 temp = SHA1CircularShift(5,A) +
 ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
 temp &= 0xFFFFFFFF;
 E = D;
 D = C;
 C = SHA1CircularShift(30,B);
 B = A;
 A = temp;
 }

 for(t = 60; t < 80; t++)
 {
 temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
 temp &= 0xFFFFFFFF;
 E = D;
 D = C;
 C = SHA1CircularShift(30,B);
 B = A;
 A = temp;
 }

 context->Message_Digest[0] =
 (context->Message_Digest[0] + A) & 0xFFFFFFFF;
 context->Message_Digest[1] =
 (context->Message_Digest[1] + B) & 0xFFFFFFFF;
 context->Message_Digest[2] =
 (context->Message_Digest[2] + C) & 0xFFFFFFFF;
 context->Message_Digest[3] =
 (context->Message_Digest[3] + D) & 0xFFFFFFFF;
 context->Message_Digest[4] =
 (context->Message_Digest[4] + E) & 0xFFFFFFFF;

 context->Message_Block_Index = 0;
}

/*
 *  SHA1PadMessage
 *
 *  Description:
 *      According to the standard, the message must be padded to an even
 *      512 bits.  The first padding bit must be a '1'.  The last 64
 *      bits represent the length of the original message.  All bits in
 *      between should be 0.  This function will pad the message
 *      according to those rules by filling the Message_Block array
 *      accordingly.  It will also call SHA1ProcessMessageBlock()
 *      appropriately.  When it returns, it can be assumed that the
 *      message digest has been computed.
 *
 *  Parameters:
 *      context: [in/out]
 *          The context to pad
 *
 *  Returns:
 *      Nothing.
 *
 *  Comments:
 *
 */
void SHA1PadMessage(SHA1Context *context)
{
 /*
 *  Check to see if the current message block is too small to hold
 *  the initial padding bits and length.  If so, we will pad the
 *  block, process it, and then continue padding into a second
 *  block.
 */
 if (context->Message_Block_Index > 55)
 {
 context->Message_Block[context->Message_Block_Index++] = 0x80;
 while(context->Message_Block_Index < 64)
 {
 context->Message_Block[context->Message_Block_Index++] = 0;
 }

 SHA1ProcessMessageBlock(context);

 while(context->Message_Block_Index < 56)
 {
 context->Message_Block[context->Message_Block_Index++] = 0;
 }
 }
 else
 {
 context->Message_Block[context->Message_Block_Index++] = 0x80;
 while(context->Message_Block_Index < 56)
 {
 context->Message_Block[context->Message_Block_Index++] = 0;
 }
 }

 /*
 *  Store the message length as the last 8 octets
 */
 context->Message_Block[56] = (context->Length_High >> 24 ) & 0xFF;
 context->Message_Block[57] = (context->Length_High >> 16 ) & 0xFF;
 context->Message_Block[58] = (context->Length_High >> 8 ) & 0xFF;
 context->Message_Block[59] = (context->Length_High) & 0xFF;
 context->Message_Block[60] = (context->Length_Low >> 24 ) & 0xFF;
 context->Message_Block[61] = (context->Length_Low >> 16 ) & 0xFF;
 context->Message_Block[62] = (context->Length_Low >> 8 ) & 0xFF;
 context->Message_Block[63] = (context->Length_Low) & 0xFF;

 SHA1ProcessMessageBlock(context);
}

int sha1_hash(const char *source, char *lrvar)
// ----------------------------------------------------------------------------
// HASH:es a string with SHA1 and stores resulting hash in lrvar variable
//
// Parameters:
//        source    Pointer to source string to HASH
//        lrvar     LR variable where base64 encoded string is stored
//
// Result
//        -1        Error
//        >0        Success. Actual value is length of HASH string
//
// Example:
//        sha1_hash( "abc", "sha1" )  // sha1=A9993E364706816ABA3E25717850C26C9CD0D89D
// ----------------------------------------------------------------------------
{
 SHA1Context sha;
 char buf[128];

 SHA1Reset(&sha);
 SHA1Input(&sha, source, strlen(source));

 if (!SHA1Result(&sha))
 {
 lr_error_message("SHA1 ERROR: Could not compute message digest");

 return -1;
 }
 else
 {
 // Clear Buffer
 memset(buf,0,sizeof(buf));

 // Store HASH in buffer
 sprintf(buf, "%08X%08X%08X%08X%08X", sha.Message_Digest[0],sha.Message_Digest[1],
 sha.Message_Digest[2],sha.Message_Digest[3],sha.Message_Digest[4]);

 // Save to LR variable
 lr_save_string(buf, lrvar);

 // Return length of string (>0 = success)
 return strlen(buf);
 }
}

About these ads

11 Responses to “SHA-1 hash for LoadRunner”

  1. James said

    Do you know of any implementations where you can create a 32 character md5 hash?

  2. David Harrison said

    Do you knwo if this has any memory issues, I am using it in a checksum system for html calls and I eventually hit 1.8gb of ram on vugen and it crashes. It seems to burn through about 1-2 MB of memusage per call (sometimes more depending on the variable size passed (i have one that is 20K characters in lenght)

    • Kim said

      During my tests this function did not eat memory in the way you describe. I’ve not especially checked the memusage on the Controller/LoadGen during tests, but endurance tests (72h or more) have been successful with millions of calls to the SHA1() functions.

      I admit that I’ve not hashed 20K worth of data so can’t really say how it’ll behave with such large char arrays..

      You also mention VuGen as the program that crashes. This implies you are running test using vugen? If so then I’m pretty sure it’s VuGen’s bugs/faults that causes the memleak & crash. VuGen is not meant for prolonged “tests” and has proven to fail in most cases when enough iterations/time has passed in a script… (this is from experience with it, no definite proof here).

  3. David Harrison said

    thanks, I haven’t tried it in controller yet, (just playing back to prove code)

    It may be a vugen problem (I am generating about 700 _sha1’s per iteration)

  4. VK said

    Hello Kim,
    I’m working on a WS-Security Password Digest and need a SHA1 Base64 encoded string. I used the code that you have listed but getting a different result. Doing a Base64 encoding of that didn’t give expected result either. Could you please help?

    Example Text to encode : test
    Expected Result: qUqP5cyxm6YcTAhz05Hph5gvu9M= (Similar result from http://webcodertools.com/hashstring)
    Result using your code: A94A8FE5CCB19BA61C4C0873D391E987982FBBD3

    Please advise. Thanks !

  5. Sushma DS said

    Hi Kim,

    There is a groovy script that is been used in our application.

    Client uses encrypted random seed value to generate HMAC code and send along random seed & HMAC code to server in Http Authorization header. Server retrieve the request from Cache to see if the seed has been issued by the server and then compares HMAC code generated by client with server generated to validate request. Handshake is implemented using interceptor using HandshakeSecurityInterceptor (BenSoft.Security). The format of authorozation Header is handshakeseed:seed,handshakehmac:hmaccode handshakeend

    Below Groovy script is used in SOAP UI tool for our project:
    /*
    * soapUI Pro, copyright (C) 2007-2011 eviware software ab
    */
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.InvalidKeyException;
    /*
    class Greet2
    {
    def name
    def log

    Greet2(who, log)
    {
    name = who;
    this.log = log
    }

    def salute() { log.info “huj tebe, $name” }
    def HMAC() {return “huj tebe, $name”}

    def static salute( who, log ) { log.info “huj tebe again $who!” }
    }
    */

    /**
    * @param secretKey
    * @param data
    * @return HMAC/SHA256 representation of the given string
    */
    def hmac_sha256(String secretKey, String data) {
    try { SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(“UTF-8″), “HmacSHA256″)
    Mac mac = Mac.getInstance(“HmacSHA256″)
    mac.init(secretKeySpec)
    byte[] digest = mac.doFinal(data.getBytes(“UTF-8″))
    return byteArrayToString(digest)
    } catch (InvalidKeyException e) { throw new RuntimeException(“Invalid key exception while converting to HMac SHA256″)
    }
    }

    private def byteArrayToString(byte[] data) {
    BigInteger bigInteger = new BigInteger(1, data)
    String hash = bigInteger.toString(16)
    //Zero pad it
    while (hash.length() < 64) {
    hash = "0" + hash
    }
    return hash
    }
    def key = testRunner.testCase.getPropertyValue("secretKey")
    log.info "key: $key"
    def response = context.expand( '${GetSeed#Response}' )
    log.info "seed: $response"
    log.info "key: $key"
    def hmac = hmac_sha256("$key", "$response")

    log.info "hmac: $hmac"
    context.x = hmac.toUpperCase()

    Similar script/logic needs to be created for Loadrunner. Can you please help?

    WIth the REST scripting in LR(web http/html), we are able to generate the GetSeedResponse. But this response has to generate HMAC/SHA256 64bit secret key which will be then feeded for the next requests.

    Please advice. Thanks!!

  6. bharat said

    Hi, Will this work if my inputs have three fileds, example -Username,Pwd and session it.?

  7. bharat said

    Getting this error whicle including and compilling my vugen script.

    Error: CCI compilation error -In file included from c:\users\bkushwah\desktop\qpass\telenore\scripts\qpass_bundle3_sell\\combined_QPass_Bundle3_Sell.c:2:
    .
    Error: CCI compilation error -globals.h:9: C:\Users\bkushwah\Desktop\QPass\Telenore\Scripts\QPass_Bundle3_Sell\lr_sha1.c: Invalid argument
    .
    Error: Vuser compilation failed. Please set CCIDebug to Off in CCI section of mdrv.dat file.
    Warning: Extension cciext.dll reports error -19797 on call to function ExtPerProcessInitialize
    Error: Thread Context: Call to service of the driver failed, reason – thread context wasn’t initialized on this thread.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: