Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / Security / March 2006

Tip: Looking for answers? Try searching our database.

Generating a MAC using a pre-shared key

Thread view: 
mclaren.bob@gmail.com - 15 Mar 2006 16:56 GMT
I have an application that requires that I validate some input
information from a third party using an HMAC hash with a specified
16-byte (32 character) key.
In my searching, I have found plenty of utility classes for doing MD5
hashing, MAC generation, etc.., but all utilities that use secret keys
seem to depend on a key generator, rather than simply accepting a
static key.

Can you point me in the right direction?
Mike Amling - 15 Mar 2006 20:33 GMT
> I have an application that requires that I validate some input
> information from a third party using an HMAC hash with a specified
[quoted text clipped - 5 lines]
>
> Can you point me in the right direction?

  Well, in about 20 lines of code you could just run the algorithm,
using the SHA-1 or MD5 MessageDigest as the only primitive you call.
HMAC(key, message) is
hash((key^outerpad)+hash((key^innerpad)+message))
where + denotes concatenation, ^ denotes XOR, and outerpad and innerpad
are bytes 0x36 and 0x5C, respectively, repeated for the length of the
key, which is padded with zeros to the block size of the hash. See the
original 1996 paper at
http://www.cse.ucsd.edu/users/mihir/papers/hmac.html or RFC 2104 at
ftp://ftp.rfc-editor.org/in-notes/rfc2104.txt.

private static byte[] hmac(byte[] key, final byte[] message,
 final int msgOffset, final int msgLength) {
  if (key.length!=64) {
    final byte[] longer=new byte[64];
    System.arraycopy(key, 0, longer, 0, key.length);
    key=longer;
  }
  final MessageDigest md5=new MessageDigest("MD5");
  for (int jj=0; jj<key.length; ++jj) {
    md5.update((byte)(key[jj]^0x5C));
  }
  for (int jj=0; jj<msgLength; ++jj) {
    md5.update(message[jj+msgOffset]);
  }
  final byte[] innerhash=md5.digest(); // Note: digest does a reset.
  for (int jj=0; jj<key.length; ++jj) {
    md5.update((byte)(key[jj]^0x36));
  }
  return md5.digest(innerhash);
}

--Mike Amling
mclaren.bob@gmail.com - 15 Mar 2006 23:05 GMT
Wow.  Thank you so much!
Your explanation makes perfect sense, and your code looks clean and
easy to understand.  This is exactly what I need!

I realize that this is only 20 lines of code, but why wouldn't this be
included in java.security as a utility class, right next to
MessageDigest?  Oh well, not your decision to defend.  It just seems
strange to me.

Anyway, thanks again for taking the time to write such a clear and
useful posting, rather than just saying, "Check out RFC 2104 ya
newb....".  You rock.
Mike Amling - 16 Mar 2006 06:56 GMT
> Wow.  Thank you so much!
> Your explanation makes perfect sense, and your code looks clean and
[quoted text clipped - 8 lines]
> useful posting, rather than just saying, "Check out RFC 2104 ya
> newb....".  You rock.

It might become cleaner yet. IIRC,
  for (int jj=0; jj<msgLength; ++jj) {
    md5.update(message[jj+msgOffset]);
  }
can be replaced with
  md5.update(message, msgOffset, msgLength);

--Mike Amling
mclaren.bob@gmail.com - 17 Mar 2006 22:34 GMT
I must admit, I'm having a little bit of trouble.  My hmac result is
not coming back the same as the third-party vendor's hmac result.

When I am creating a MAC for a String, do I simply use
myString.getBytes() in order to feed it to this hmac routine?  I
assumed so, but I can't figure out why I'm getting the wrong results.

Also just to note, I am using the following routines to convert values
between byte arrays and hex strings. (My key is provided as a hex
string, and the mac hash needs to be communicated back as a hex string)

// Fast convert a byte array to a hex string
// with possible leading zero.
public static String toHexString ( byte[] b )
  {
  StringBuffer sb = new StringBuffer( b.length * 2 );
  for ( int i=0; i<b.length; i++ )
     {
     // look up high nibble char
     sb.append( hexChar [( b[i] & 0xf0 ) >>> 4] );

     // look up low nibble char
     sb.append( hexChar [b[i] & 0x0f] );
     }
  return sb.toString();
  }

// table to convert a nibble to a hex char.
static char[] hexChar = {
  '0' , '1' , '2' , '3' ,
  '4' , '5' , '6' , '7' ,
  '8' , '9' , 'a' , 'b' ,
  'c' , 'd' , 'e' , 'f'};

/**
* Convert a hex string to a byte array.
* Permits upper or lower case hex.
*
* @param s String must have even number of characters.
* and be formed only of digits 0-9 A-F or
* a-f. No spaces, minus or plus signs.
* @return corresponding byte array.
*/
public static byte[] fromHexString ( String s )
  {
  int stringLength = s.length();
  if ( (stringLength & 0x1) != 0 )
     {
     throw new IllegalArgumentException ( "fromHexString requires an
even number of hex characters" );
     }byte[] b = new byte[stringLength / 2];

  for ( int i=0,j=0; i<stringLength; i+=2,j++ )
     {
     int high = charToNibble( s.charAt ( i ) );
     int low = charToNibble( s.charAt ( i+1 ) );
     b[j] = (byte)( ( high << 4 ) | low );
     }
  return b;
  }

/**
* convert a single char to corresponding nibble.
*
* @param c char to convert. must be 0-9 a-f A-F, no
* spaces, plus or minus signs.
*
* @return corresponding integer
*/
private static int charToNibble ( char c )
  {
  if ( '0' <= c && c <= '9' )
     {
     return c - '0';
     }
  else if ( 'a' <= c && c <= 'f' )
     {
     return c - 'a' + 0xa;
     }
  else if ( 'A' <= c && c <= 'F' )
     {
     return c - 'A' + 0xa;
     }
  else
     {
     throw new IllegalArgumentException ( "Invalid hex character: " +
c );
     }
  }
Mike Amling - 18 Mar 2006 02:30 GMT
> I must admit, I'm having a little bit of trouble.  My hmac result is
> not coming back the same as the third-party vendor's hmac result.
[quoted text clipped - 6 lines]
> between byte arrays and hex strings. (My key is provided as a hex
> string, and the mac hash needs to be communicated back as a hex string)

  On first glance, the utility routines look OK. I assume you checked
that I didn't get the inner pad and outer pad reversed, and that it's to
use MD5, as opposed to SHA-1. Do you have an example of a key and a
message and two different HMAC results?

--Mike Amling
Mr. Skeptic - 18 Mar 2006 19:38 GMT
Just use the MAC class, and use the SecretKeySpec class for the Key.
SecretKeySpec implements Key.
mclaren.bob@gmail.com - 20 Mar 2006 16:31 GMT
Well that did it.  I knew there had to be a way without having to write
my own HMAC implementation.  Allthough this wasn't half as fun as
trying to build it myself. ;)
I thank you, my customer thanks you.

For those interested, here is the code I used:

byte[] sKeyBytes=fromHexString("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
javax.crypto.spec.SecretKeySpec skey = new
javax.crypto.spec.SecretKeySpec(sKeyBytes,"HmacMD5");
javax.crypto.Mac mac =
javax.crypto.Mac.getInstance(skey.getAlgorithm());
mac.init(skey);
String plaintext="Hi There";
byte[] textbytes=plaintext.getBytes("UTF-8");
byte[] results=mac.doFinal(textbytes);
String strResults=toHexString(results);
System.out.println("HMAC:"+ strResults);
//Results show 9294727a3638bb1c13f48ef8158bfc9d
// as per test vectors in ftp://ftp.rfc-editor.org/in-notes/rfc2104.txt

This worked like a charm, although I must say that I still have an itch
to know why Mike Amling's custom implementation didn't work.  The whole
thing appears to be sound, but I just couldn't find the bug.
Mike Amling - 20 Mar 2006 18:36 GMT
> Well that did it.  I knew there had to be a way without having to write
> my own HMAC implementation.  Allthough this wasn't half as fun as
[quoted text clipped - 20 lines]
> to know why Mike Amling's custom implementation didn't work.  The whole
> thing appears to be sound, but I just couldn't find the bug.

  I had exchanged the inner pad and the outer pad. This code, which has
now actually been tested(!), works better:

private static final int COMPRESSION_FUNCTION_BYTES=64;
private static byte[] hmacMd5(final byte[] key, final byte[] message,
 final int msgOffset, final int msgLength) {
    final MessageDigest md5=new Md5();
    for (int jj=0; jj<COMPRESSION_FUNCTION_BYTES; ++jj) {
        md5.update((byte)(jj<key.length?key[jj]^0x36:0x36));
    }
    md5.update(message, msgOffset, msgLength);
    final byte[] innerhash=md5.digest(); // Note: digest does a reset.
    for (int jj=0; jj<COMPRESSION_FUNCTION_BYTES; ++jj) {
        md5.update((byte)(jj<key.length?key[jj]^0x5C:0x5C));
    }
    return md5.digest(innerhash);
}

--Mike Amling
Mike Amling - 20 Mar 2006 19:19 GMT
>> ...
>> This worked like a charm, although I must say that I still have an itch
>> to know why Mike Amling's custom implementation didn't work.  The whole
>> thing appears to be sound, but I just couldn't find the bug.
>
>   I had exchanged the inner pad and the outer pad.

  I was using the ipad=5C opad=36 in the original 1996 paper,
http://www.cse.ucsd.edu/users/mihir/papers/kmd5.pdf, which says

  HMAC<sub>k</sub>(x) = F(k' XOR opad, F(k' XOR ipad, x))
where k' is the completion by adding 0's of k to a full b-bit block-size
of the iterated hash function, opad and ipad are two fixed b-bit
constants (the "i" and "o" are mnemonics for inner and outer), XOR is
the bitwise Exclusive Or operator, and the commas represent
concatenation of the information. opad is formed by repeating the byte
x'36' as many times as needed to get a b-bit block, and ipad is defined
similarly using the byte x'5c'.
<<

In contrast, RFC 2104 at ftp://ftp.rfc-editor.org/in-notes/rfc2104.txt 
says ipad=36 opad=5C:

ipad = the byte 0x36 repeated B times
opad = the byte 0x5C repeated B times.
To compute HMAC over the data `text' we perform
H(K XOR opad, H(K XOR ipad, text))
<<

--Mike Amling
mclaren.bob@gmail.com - 23 Mar 2006 16:37 GMT
Slick!

> > Well that did it.  I knew there had to be a way without having to write
> > my own HMAC implementation.  Allthough this wasn't half as fun as
[quoted text clipped - 40 lines]
>
> --Mike Amling
mclaren.bob@gmail.com - 23 Mar 2006 16:39 GMT
mclaren.bob@gmail.com - 20 Mar 2006 15:54 GMT
Indeed that was the first thing I checked.  Since starting this
discussion I have learned a LOT about cryptography and MAC hashes, but
unfortunately I just can't figure out where my/our code is going wrong.

For testing, rather than rely on my third-party vendor having the right
hash result, I actually used the test vectors found in the RFC
reference you sent me. (ftp://ftp.rfc-editor.org/in-notes/rfc2104.txt)

 key =         0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
 key_len =     16 bytes
 data =        "Hi There"
 data_len =    8  bytes
 digest =      0x9294727a3638bb1c13f48ef8158bfc9d

 key =         "Jefe"
 data =        "what do ya want for nothing?"
 data_len =    28 bytes
 digest =      0x750c783e6ab0b503eaa86e310a5db738
mclaren.bob@gmail.com - 20 Mar 2006 17:01 GMT
P.S.
I also found an alternative to the custom fromHexString and toHexString
functions I was using.
com.sun.org.apache.xerces.internal.impl.dv.util.HexBin appears to be
part of the JDK, and worked like a charm.


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.