cancel
Showing results for 
Search instead for 
Did you mean: 

Working php hash verification

**If you find this helpful please kudo this post. This has consumed a huge chunk of my day and you will help me build credibility for when I enroll in the authorize.net certified developer program. **

 

Here is 100% tested, working php hash verification code for the php SDK. I believe this will also work with SIM/AIM, etc.

 

You need the following to have an apples to apples setup with what I used:

 

1: The most recent php SDK package from GitHub. I downloaded this today and installed. I believe it is a few days old.

 

2: If you have not generated a signature key from your production or sandbox merchant interface to use for testing, do so. You won’t get the hash in the response without it. Generate it and copy it for use in this script.

 

3: An API call script for some payment transaction that returns the hash. With the SDK I am getting this for voidTransaction, refundTransaction, capture, etc. I believe that any payment function that directly charges or affects a transaction will contain this. The Accept Hosted form API call obviously does not.

 

For requirement 1, the SIM/DPM, etc. users do not have this, if my understanding is correct. You should be able to use this as well, only substituting my method for extracting the transHashSha2 value from the response with however you accomplish this using your integration. You may also have to use different parameters in your delimited string, I would try this method first, but I have seen other developers posting attempts with more fields in the string than login, transId, and amount, and there is probably a good reason for this. 

 

 

Here is the code (p.s. do not follow the hyperlink to the C# byte array description and try to implement a php equivalent to the C# byte array script. This makes things 100X harder than they have to be, as I know well at this point. Without further delay…..)

 

 

$login = "copy and paste your merchant login id here";
$signatureKey ="copy and paste your signature key here";
$signatureKey = hex2bin($signatureKey);
$amount = $amount;

//$response stands for the response object returned by your API call
//e.g.  $response = refundTransaction($refTransId,$amount,$lastFour);

$transId = $response->getTransactionResponse()->getTransId();
$string = '^'.$login.'^'.$transId.'^'.$amount.'^';


$hash = $response->getTransactionResponse()->getTransHashSha2();
$digest = strtoupper(HASH_HMAC('sha512',$string,$signatureKey));

if(hash_equals ($digest,$hash)){

    //This if statement is the verification piece 
    //Put whatever you want your app to do with the transaction here
    //to test you can do something like echo "Hash verification validated";
    //or try this:
    //$dump = print_r($string,true);
    //$fp = file_put_contents( 'transhash.log', $dump );
    //and if your directory populates with a file named transhash.log you know 
    //verification succeeded
    

}

 

Renaissance
All Star
67 REPLIES 67

@xero

 

Alright, so I plugged in your values and I do not get the same result. So something is wrong with the Java library. What's weird is I plug your values into this online generator and I get your result:

 

https://cryptii.com/pipes/hmac

 

But if I plug it into this one I get my result:

 

https://codebeautify.org/hmac-generator

 

So my library is doing something different just like that second link but I have no idea what. I've tried encodings, unsigned. Anyone have any idea? And why would this exact same library work fine for the original AuthorizeNet MD5.

lightwave365
Regular Contributor

@lightwave365

 

The code example provided by authorize use ASCII Encoding.

Also I was running my tests and notice one of the test failed due to the address field containing special char like "Pinière" which caused the compare hash to fail. 

@xero

 

You are a god send!! Seriously, holy shit I can't believe how much you helped me with just a simple input/output example.

 

Because of that I was able to narrow down that my hashing method wasn't working then after MANY stack overflow pages and random articles online I found this one:

 

https://stackoverflow.com/questions/11670542/java-hmac-sha512-generation

 

Where it says I need to 'pack' the key before using it in the constructor. And after I did that I was able to get your result. So as a sanity check by using your example I knew it was correct the second I saw it without having to keep sending requests to AuthNet every time.

 

This is the final code:

 

String generatedHash = hashStringWithMD5( "^0^false^3^^^^P^^^1.99^^^^^^^^^^^^^^^^^^^^^", "9C5A4D2AFE1D1D5DB3A8FC4C95CDCF49E2B052B4220D0624C54C1C662194BDEF8FE0EA27B313FA62328D9500D123B9DD3CE06644508803ACD04DAEDB24C5D122", HMAC_SHA512_ALGORITHM, DEFAULT_ENCODING ).toUpperCase();
boolean passHashAuthentication = generatedHash.equals( getMD5HashFromAuthorizeNet() );


private String hashStringWithMD5( String toBeHashed, String secretKey, String algorithm, String encoding ) { if( logger.isDebugEnabled() ) { logger.debug( "Attempting to hash string [" + toBeHashed + "] with SecretKey [" + secretKey + "] with Algorithm [" + algorithm + "]" ); } StringBuilder strbuf = new StringBuilder(); try { byte[] secretKeyBytes; if( algorithm.equals( HMAC_MD5_ALGORITHM ) ) { secretKeyBytes = secretKey.getBytes(); } else { secretKeyBytes = DatatypeConverter.parseHexBinary(secretKey); } SecretKeySpec key = new SecretKeySpec( secretKeyBytes, algorithm ); Mac mac = Mac.getInstance( algorithm ); mac.init(key); byte[] digest = mac.doFinal(toBeHashed.getBytes( StandardCharsets.UTF_8 ) ); for (byte aByte : digest) { int anUnsignedByte = aByte & 0xff; String convertedByte = Integer.toHexString(anUnsignedByte); if (convertedByte.length() == 1) convertedByte = "0" + convertedByte; strbuf.append(convertedByte); } } catch ( NoSuchAlgorithmException e ) { logger.debug( "Unsupported algorithm: [" + algorithm + "] " + DebugUtils.debugException( e ) ); } catch ( InvalidKeyException e ) { logger.debug( "Invalid Key " + DebugUtils.debugException( e ) ); } return strbuf.toString(); }

Again man, thank you so much. I can now move on with my life.

lightwave365
Regular Contributor
regarding the silent post hash I am left to only guess. All I know is that when I searched the support site it said nothing about it at all. Only the MD5 stuff from before. Let me know how it goes for you. The ARB thing I’m also sort of clueless about. Never have used it. The only 2 things I observed are that it says to use webhooks, and that the response on the main api reference doesn’t list a hash at all.

The pattern I have seen in my testing of the API referenced methods from the actual website is that any of those that list the transHash will also now return the sha512. And I agree with you that logically it would only make sense for the silent post to return a sha512 like the rest of them. It just seems like that product is the least supported of any they have. I would start one hash at a time and do silent post last. Once you know that the other various hashes are working you can test each method to see which works. Its a stab in the dark but at least you’ll have something to stab with.

Please help me out - 

I am not getting it work.

see code change - 

while generating fingerprint for x_hp_hash -

from md5 - 

if (function_exists('hash_hmac')) {
   return hash_hmac("md5", $api_login_id . "^" . $fp_sequence . "^" . $fp_timestamp . "^" . $amount . "^", $transaction_key); 
}
            
return bin2hex(mhash(MHASH_MD5, $api_login_id . "^" . $fp_sequence . "^" . $fp_timestamp . "^" . $amount . "^", $transaction_key));

to sha512 - 

$signature_key = hex2bin($signature_key);
            
if (function_exists('hash_hmac')) {
  return hash_hmac("sha512", $api_login_id . "^" . $fp_sequence . "^" . $fp_timestamp . "^" . $amount . "^", $signature_key); 
}
return bin2hex(mhash(MHASH_SHA512, $api_login_id . "^" . $fp_sequence . "^" . $fp_timestamp . "^" . $amount . "^", $signature_key));


While getting reponse and comparing the x_sha_hash value
 from md5 -

if(strtoupper(md5($this->md5_setting . $this->api_login_id . $this->transaction_id . $amount)) == $this->md5_hash){
            //valid
 } else{
            //not valid
}

Changed to sha512 - 

$this->signature_key = hex2bin($this->signature_key);
$string = '^'.$this->api_login_id.'^'.$this->transaction_id.'^'.$amount.'^';
if(strtoupper(HASH_HMAC('sha512', $string, $this->signature_key)) == $this->SHA2_Hash){
      //valid
 } else{
      //not valid
 }

What I am doing wrong?
please help me out. Not working for me :-(

I’ll be on and off for a little bit but just looking at this for a second your fingerprint is missing a caret at the beginning of the string
So for your fingerprint you use one thing and for your verification you use another. Looking at the DPM/SIM guide you have to use a very large delimited string that has 31 carets. Lightwave and others have posted about this and I believe there are quite a few example codes floating around for that. I pulled the guide and made some sample php code for sim/DPM that I think should work. It is a few pages back. Looks like you might have to toss out the strtoupper I posted. Not sure.

@pudhgdfhgdf

you saved me :-)

Thanks a lot

I used all the fields for generating hashString and it worked finally :-)


For anyone else going bananas trying to get this to work I was able to get a successful hash/digest pair using the method @Renaissance lays out but only after figuring out a couple of things and running into walls . Couple of notes:

 

1. as already indicated in prior posts on this thread, @Renaissance 's original solution is for API ONLY. All of the 30+ caret delimited digest strings do NOT pertain to API. API should only have three values with four carets: ^apiloginid^transID^amount^

 

2. It appears the upgrade guide needs to be modified. It asks you for "The transaction amount that we send in createTransactionResponse in the amount element" to be included in the digest string but the response from the API doesn't return amount in the response. Seems it should read "The transaction amount that you send in createTransactionRequest in the transactionRequest > amount element". WIll avoid headaches with anyone thinking they should just leave that part of the string empty since they didn't get amount element in the response.

 

3. Your signature key could possibly be conflicting if an old one exists. I didn't get it to work until I generated a new signature key AND selected the option to have the existing one(s) IMMEDIATELY disabled. Prior I would generate a new one but not select the "disable" option.

 

4. Your signature key via the authorize.net admin interface will generate with a line break that carries over if you click the copy button. Make sure to remove the line break if you use the button or just copy the actual text itself. The signature should NOT have a line break at the beginning.

 

Hope this helps someone else avoid some headaches. Good luck!

thank you @darksteppez for clearing up some of those details for me. I was finally able to get my hash to match the response hash.

 

thank you so much @Renaissance for your posts, I would have been stumbling around in the dark forever if I didn't find this thread.

 

One thing that was tripping me up was the number format for the "amount" field when using the API; it didn't work for me until I had 2 decimals. My transactions are almost always in whole dollar amounts, which works fine in the API, but when calculating the hash, I had to use 2 decimals:

$amount = number_format($amount, 2);

Anyway, thanks again, and best of luck to everyone.