+ Start a Discussion
@altius_rup@altius_rup 

Crypto SHA512 HMAC with binary hex key - Apex equivalent of PHP/Java example

Hi,

I'm trying to reproduce in APEX this PHP example :

<?php 
$key
="0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
$binkey
= pack("H*", $key);
echo strtoupper
(hash_hmac('sha512',"ABC", $binkey));
?>

 

or this Java example :

privateString generateHMAC(String datas )
   
{

       
//                final Charset asciiCs = Charset.forName( "utf-8" );
       
Mac mac;
       
String result ="";
       
try
       
{
            finalSecretKeySpec secretKey = newSecretKeySpec( DatatypeConverter.parseHexBinary(CONSTANTS.KEY),"HmacSHA512");
            mac
=Mac.getInstance("HmacSHA512");
            mac
.init( secretKey );
           
finalbyte[] macData = mac.doFinal( datas.getBytes());
           
byte[] hex =newHex().encode( macData );
            result
=newString( hex,"ISO-8859-1");
       
}

       
return result.toUpperCase();

   
}

 

This is my APEX code :

private String hmacFunction(String str, String k) {
    Blob mac = Crypto.generateMac('hmacSHA512', Blob.valueOf(str), Blob.valueOf(k));
    return EncodingUtil.convertToHex(mac).toUpperCase();
}

 

This is no good, for the moment : encrypted strings match between Java and PHP, not with APEX.

The result for ABC should be :

100A6A016A4B21AE120851D51C93B293D95B7D8A44B16ACBEFC2D1C9DF02B6F54FA3C2D6802E52FED5DF8652DDD244788A204682D2D1CE861FDA4E67F2792643

 

Obviously, the difficulty lies in the pack('H*, key) conversion which seems to be different from the Blob.valueOf(key) call.

 

 

Can anyone help ?

 

Rup

 

 

Best Answer chosen by Admin (Salesforce Developers) 
Pat PattersonPat Patterson

The problem is that Blob.valueOf(key) does not do the hex decode - it just gives you the ascii bytes of the input. Unfortunately there is not (yet) an EncodingUtil.convertFromHex(), so the only way to create a binary key from string input is base64.

 

I generated the correct base64 encoding with php:

 

$key ="0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
$binkey = pack("H*", $key); 
echo base64_encode ( $binkey )."\n"; 

And this Apex gives the correct result:

 

String base64key = 'ASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7w==';
String str = 'ABC';
Blob mac = Crypto.generateMac('hmacSHA512', Blob.valueOf(str), EncodingUtil.base64decode(base64key));
System.debug(EncodingUtil.convertToHex(mac).toUpperCase());

Output:

11:19:36:036 USER_DEBUG [4]|DEBUG|100A6A016A4B21AE120851D51C93B293D95B7D8A44B16ACBEFC2D1C9DF02B6F54FA3C2D6802E52FED5DF8652DDD244788A204682D2D1CE861FDA4E67F2792643

Cheers,

 

Pat

All Answers

Pat PattersonPat Patterson

The problem is that Blob.valueOf(key) does not do the hex decode - it just gives you the ascii bytes of the input. Unfortunately there is not (yet) an EncodingUtil.convertFromHex(), so the only way to create a binary key from string input is base64.

 

I generated the correct base64 encoding with php:

 

$key ="0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
$binkey = pack("H*", $key); 
echo base64_encode ( $binkey )."\n"; 

And this Apex gives the correct result:

 

String base64key = 'ASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7w==';
String str = 'ABC';
Blob mac = Crypto.generateMac('hmacSHA512', Blob.valueOf(str), EncodingUtil.base64decode(base64key));
System.debug(EncodingUtil.convertToHex(mac).toUpperCase());

Output:

11:19:36:036 USER_DEBUG [4]|DEBUG|100A6A016A4B21AE120851D51C93B293D95B7D8A44B16ACBEFC2D1C9DF02B6F54FA3C2D6802E52FED5DF8652DDD244788A204682D2D1CE861FDA4E67F2792643

Cheers,

 

Pat

This was selected as the best answer
@altius_rup@altius_rup

Wow, thanks for your reactivity !

 

But how did you produce the base64key ?

 

Rup

@altius_rup@altius_rup

Pat,

Sorry for the stupid question. It should have been :

 

How can I create that base64key in APEX, or any other way (call out to a webservice) ?

 

Rup

rtuttlertuttle

It has been a while but you can convert from hex to integer array, then integer array to string.  I wrote a function that does it.  I shared it previously here and there is also a multi-byte version on there as well from another guy. I also have a multi-byte utility function and can send if necessary, but his looks right.  The function you want to see use with it is String.fromCharArray(List<Integer>)

 

http://boards.developerforce.com/t5/Apex-Code-Development/Method-to-convert-Hex-to-String-or-blob/m-p/482297#M88828

Pat PattersonPat Patterson

Hi Richard,

 

Does that hex->string method work for arbitrary hex-encoded binary input? It seems like it would fail for certain input, since not all binary input are valid multibyte strings.

 

Cheers,


Pat

rtuttlertuttle

Hi Pat,

 

From what I've seen/tested it does work even for odd binary characters, but of course would be unreadable.  I used this method to write my own AES encryption utility (ported Bouncy Castle) prior to there being encryption in Apex.

rtuttlertuttle

Although for arbitrary keys like this I'd just use the standard hex to integer that I posted on that post instead of the multibyte.

Pat PattersonPat Patterson

Awesome - I can abandon the String hexToBase64(String hexInput) method I was considering writing! :-)

Pat PattersonPat Patterson

Hmm - I wrote a quick test routine:

    static String hexChars = '0123456789abcdef';

@isTest private static void testHexToInt() { String input = ''; for (Integer higher = 0; higher < 16; higher++) { for (Integer lower = 0; lower < 16; lower++) { input += (hexChars.substring(higher, higher+1) + hexChars.substring(lower, lower+1)); } } System.debug(input); Integer[] output = hexToInt(input); for (Integer i = 0; i < input.length() / 2; i++) { System.assertEquals(i, output[i]); } Blob b = Blob.valueOf(String.fromCharArray(output)); System.assertEquals(input, EncodingUtil.convertToHex(b)); }

 But the last assert fails:

 

21:46:14:717 FATAL_ERROR System.AssertException: Assertion Failed: Expected: 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff, Actual: 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7fc280c281c282c283c284c285c286c287c288c289c28ac28bc28cc28dc28ec28fc290c291c292c293c294c295c296c297c298c299c29ac29bc29cc29dc29ec29fc2a0c2a1c2a2c2a3c2a4c2a5c2a6c2a7c2a8c2a9c2aac2abc2acc2adc2aec2afc2b0c2b1c2b2c2b3c2b4c2b5c2b6c2b7c2b8c2b9c2bac2bbc2bcc2bdc2bec2bfc380c381c382c383c384c385c386c387c388c389c38ac38bc38cc38dc38ec38fc390c391c392c393c394c395c396c397c398c399c39ac39bc39cc39dc39ec39fc3a0c3a1c3a2c3a3c3a4c3a5c3a6c3a7c3a8c3a9c3aac3abc3acc3adc3aec3afc3b0c3b1c3b2c3b3c3b4c3b5c3b6c3b7c3b8c3b9c3bac3bbc3bcc3bdc3bec3bf

The output is as expected until input 80, for which the output is c280 :-/

 

Am I missing something?

rtuttlertuttle

You're right, as soon as it crosses the 127 border it goes into multibyte territory and falls apart.  Hmm...  I'm gonna take another stab from another angle.

rtuttlertuttle

Okay, here is another approach.  Its the best I could think of given the multibyte quandary.

 

public with sharing class HexUtils {
    public static String asciiHexToBase64(String inputHex) {
        Map<String,Integer> hexMap = new Map<String,Integer>();
            hexMap.put('0',0);hexMap.put('B',11);
            hexMap.put('1',1);hexMap.put('C',12);
            hexMap.put('2',2);hexMap.put('D',13);
            hexMap.put('3',3);hexMap.put('E',14);
            hexMap.put('4',4);hexMap.put('F',15);
            hexMap.put('5',5);hexMap.put('a',10);
            hexMap.put('6',6);hexMap.put('b',11);
            hexMap.put('7',7);hexMap.put('c',12);
            hexMap.put('8',8);hexMap.put('d',13);
            hexMap.put('9',9);hexMap.put('e',14);
            hexMap.put('A',10);hexMap.put('f',15);
        List<Integer> hexInts = new List<Integer>();
        for(Integer i=0;i<inputHex.length();i+=2) {
            hexInts.add( (hexMap.get(inputHex.substring(i,i+1))*16) + (hexMap.get(inputHex.substring(i+1,i+2))) );
        }
        Map<Integer,String> base64Map = new Map<Integer,String>();
        base64Map.put(0,'A');base64Map.put(26,'a');base64Map.put(52,'0');
        base64Map.put(1,'B');base64Map.put(27,'b');base64Map.put(53,'1');
        base64Map.put(2,'C');base64Map.put(28,'c');base64Map.put(54,'2');
        base64Map.put(3,'D');base64Map.put(29,'d');base64Map.put(55,'3');
        base64Map.put(4,'E');base64Map.put(30,'e');base64Map.put(56,'4');
        base64Map.put(5,'F');base64Map.put(31,'f');base64Map.put(57,'5');
        base64Map.put(6,'G');base64Map.put(32,'g');base64Map.put(58,'6');
        base64Map.put(7,'H');base64Map.put(33,'h');base64Map.put(59,'7');
        base64Map.put(8,'I');base64Map.put(34,'i');base64Map.put(60,'8');
        base64Map.put(9,'J');base64Map.put(35,'j');base64Map.put(61,'9');
        base64Map.put(10,'K');base64Map.put(36,'k');base64Map.put(62,'+');
        base64Map.put(11,'L');base64Map.put(37,'l');base64Map.put(63,'/');
        base64Map.put(12,'M');base64Map.put(38,'m');base64Map.put(-1000,'=');
        base64Map.put(13,'N');base64Map.put(39,'n');
        base64Map.put(14,'O');base64Map.put(40,'o');
        base64Map.put(15,'P');base64Map.put(41,'p');
        base64Map.put(16,'Q');base64Map.put(42,'q');
        base64Map.put(17,'R');base64Map.put(43,'r');
        base64Map.put(18,'S');base64Map.put(44,'s');
        base64Map.put(19,'T');base64Map.put(45,'t');
        base64Map.put(20,'U');base64Map.put(46,'u');
        base64Map.put(21,'V');base64Map.put(47,'v');
        base64Map.put(22,'W');base64Map.put(48,'w');
        base64Map.put(23,'X');base64Map.put(49,'x');
        base64Map.put(24,'Y');base64Map.put(50,'y');
        base64Map.put(25,'Z');base64Map.put(51,'z');
        String output = '';
        for(Integer j=0;j<Math.ceil(Double.valueOf(hexInts.size())/3);j++) {
            List<Integer> g = new Integer[3];
            g[0]=hexInts[j*3];
            g[1]=(j*3+1<hexInts.size() ? hexInts[j*3+1] : -1000);
            g[2]=(j*3+2<hexInts.size() ? hexInts[j*3+2] : -1000);
            List<Integer> g2 = new Integer[4];
            g2[0]=g[0]>>2;
            g2[1]=((g[0]&3)<<4)|((g[1]==-1000?0:g[1]>>4));
            g2[2]=(g[1]==-1000?-1000:((g[1]&15)<<2)|(g[2]==-1000?0:(g[2]>>6)));
            g2[3]=(g[2]==-1000?-1000:g[2]&63);
            for(Integer g2x : g2) {
                output+=base64Map.get(g2x);
            }
        }
        return output;
    }
}

 

I compared against Pat's Base64 value using an assertion and it works as expected.  Can you two confirm on your end?

rtuttlertuttle

And ironically enough I missed your message about writing a function just like this Pat.  :)

 

Good to know my thought process was along the same lines.  Hope this helps.

@altius_rup@altius_rup

Richard,

I tested your code on the few examples I have, and it's OK for me.

 

Thanks both for helping me solve this problem I have been trying to understand for the last 3 weeks !

 

BR,

Rup

KevanMKevanM
It is now possible to generate hmac from hex key in apex. Here is the code: 
String key = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF';
String str = 'ABC';
Blob mac = Crypto.generateMac('hmacSHA512', Blob.valueOf(str), EncodingUtil.convertFromHex(key));
System.debug(EncodingUtil.convertToHex(mac).toUpperCase());

Output:
18:31:28:002 USER_DEBUG [4]|DEBUG|100A6A016A4B21AE120851D51C93B293D95B7D8A44B16ACBEFC2D1C9DF02B6F54FA3C2D6802E52FED5DF8652DDD244788A204682D2D1CE861FDA4E67F2792643

Cheer,
Kevan Moothien