views:

700

answers:

2

Hi, I have to implement the HMAC MD5 in my iPhone app. The PHP version of the algorithm (implemented server side for verification) is here and I can't modify it (it's an API)

function hmac($key, $data) {
 $b = 64; // byte length for md5
 if (strlen($key) > $b) {
  $key = pack("H*",md5($key));   
 }
 $key = str_pad($key, $b, chr(0x00));
 $ipad = str_pad('', $b, chr(0x36));
 $opad = str_pad('', $b, chr(0x5c));
 $k_ipad = $key ^ $ipad ;
 $k_opad = $key ^ $opad;
 $message = $k_opad . pack("H*",md5($k_ipad . $data));
 return base64_encode(md5($message));
}

I found a couple of objective-C implementation :

- (NSString *)HMACMD5WithKey:(NSString *)key andData:(NSString *)data
{

    const char *cKey  = [key cStringUsingEncoding:NSASCIIStringEncoding]; 
    const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];
    unsigned char cHMAC[CC_MD5_DIGEST_LENGTH];
    CCHmac(kCCHmacAlgMD5, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
    NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
    NSString *hash = [Base64 encode:HMAC];
    return hash;
}

Didn't return the same results (PHP != ObjC).

I played with ObjC implementations changing the digest length to 32 (result then has the same length than PHP implementation), the key length to 64 (corresponding to the first str_pad) but the results are always differents.

Can someone tell me how to have the same result in Objective-C ??

Edit: since the 2 implementations in ObjC return the same result only one is useful here..

+1  A: 

First, your “ObjC2” is using SHA1, not MD5 (this means you are probably getting a buffer overrun since SHA1 is 20 bytes, while MD5 is 16 bytes).

Second, I think there is a non-standard variation in your PHP HMAC implementation. Notice how all but the last invocations of md5() are wrapped with a pack("H*",…)? All but the last one right before Base64 encoding, that is. I think this means that the PHP code is Base64 encoding the 'printable hex' representation of the data (32 bytes, each a hex digit in ASCII) not the 'raw' value (16 bytes).

Since you cannot change the PHP implementation, you will have to write an HMAC implementation in Objective C that has this same non-standard variation.

Chris Johnsen
There's no need to make this answer Community Wiki. Community Wiki answers don't earn any reputation, and are usually used for non-technical questions.
Artelius
First, thanks for the answer ! You're right ObjC had SHA1 instead of MD5 algorithm. But my problem stay, how can I re-implement the PHP non-standard variation ? I can implement from strach the str_pad and the ^(XOR) functions but the pack-one remain not-understandable for me !
Fsdn
@Fsdn: Right now, I do not have the time to write the ObjC code to test my theory (encoding hex instead of raw). Maybe someone else will take a crack at it or I'll pick it up later. (This is also why I made it community wiki, my answer just gives a theory and is incomplete.)
Chris Johnsen
+2  A: 

As per my previous answer, the PHP code implements a non-standard variant of the HMAC algorithm. This Objective C code should imitate it. I tested it on Mac OS X 10.4.11 against the PHP code for two combinations:

"a short key"
"some text"

"This is a very long key. It is longer than 64 bytes, which is the important part."
"This is some very long text. It is longer than 64 bytes, which is the important part."

- (NSString *)HMACMD5WithKey:(NSString *)key andData:(NSString *)data {
    const char *cKey  = [key cStringUsingEncoding:NSASCIIStringEncoding];
    const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];
    const unsigned int blockSize = 64;
    char ipad[blockSize], opad[blockSize], keypad[blockSize];
    unsigned int keyLen = strlen(cKey);
    CC_MD5_CTX ctxt;
    if(keyLen > blockSize) {
        //CC_MD5(cKey, keyLen, keypad);
        CC_MD5_Init(&ctxt);
        CC_MD5_Update(&ctxt, cKey, keyLen);
        CC_MD5_Final((unsigned char *)keypad, &ctxt);
        keyLen = CC_MD5_DIGEST_LENGTH;
    } else {
        memcpy(keypad, cKey, keyLen);
    }
    memset(ipad, 0x36, blockSize);
    memset(opad, 0x5c, blockSize);

    int i;
    for(i = 0; i < keyLen; i++) {
      ipad[i] ^= keypad[i];
      opad[i] ^= keypad[i];
    }

    CC_MD5_Init(&ctxt);
    CC_MD5_Update(&ctxt, ipad, blockSize);
    CC_MD5_Update(&ctxt, cData, strlen(cData));
    unsigned char md5[CC_MD5_DIGEST_LENGTH];
    CC_MD5_Final(md5, &ctxt);

    CC_MD5_Init(&ctxt);
    CC_MD5_Update(&ctxt, opad, blockSize);
    CC_MD5_Update(&ctxt, md5, CC_MD5_DIGEST_LENGTH);
    CC_MD5_Final(md5, &ctxt);

    const unsigned int hex_len = CC_MD5_DIGEST_LENGTH*2+2;
    char hex[hex_len];
    for(i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
        snprintf(&hex[i*2], hex_len-i*2, "%02x", md5[i]);
    }

    NSData *HMAC = [[NSData alloc] initWithBytes:hex length:strlen(hex)];
    NSString *hash = [Base64 encode:HMAC];
    [HMAC release];
    return hash;
}
Chris Johnsen
Wow, thank you very very much !!!
Fsdn