Java Forum / Security / March 2006
Generating a MAC using a pre-shared key
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 MagazinesGet 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 ...
|
|
|