**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 }
Solved! Go to Solution.
01-15-2019 09:04 PM - edited 01-15-2019 09:13 PM
I understand that things change and are deprecated. It is extremely unreasonable for me to review their documentation every once in a while for changes.
1) I have a full-time job. It's over 100 pages long and they have 4 different documentations. I don't have time to check and see what changed with their API. They should be giving us announcements via emails (and not just to our customers, the actual developers need to know this too).
2) Knowing AuthorizeNet I don't trust them to update their documentation. They've done a shitty job the past 5 years why would they be any better now? We literally have 4 different documentations of theirs with all conflicting information at this point:
https://developer.authorize.net/support/hash_upgrade/
https://developer.authorize.net/api/reference/index.html
https://www.authorize.net/content/dam/authorize/documents/SIM_guide.pdf
https://api.authorize.net/xml/v1/schema/AnetApiSchema.xsd
NONE of them had that x_sha2_hash parameter. One says 30 item hash, one says 3 item hash. What the fck do they want from us?!
3) I am implementing the Accept Hosted, but if anyone knows how the software cycle works you know you don't just blindy remove an older version. You phase it out. Something they failed to do here. And phasing it out doesn't mean release an email that it's leaving in 2 weeks. You give several months notice. I am keeping around SIM in my software as a legacy option, but encouraging they use the Accept Hosted. I know how a software cycle works, they don't. Which is sad because this is a multi-billion dollar company. You'd think they have some competence.
Again, not blaming you for any of this. You don't work there. Though I don't know why you'd want to be a certified developer for this place. There are so many better places to work.
After this whole debacle and incompetence and lack of wanting to help their customers I will be steering all my customers away from AuthorizeNet now and convincing them of some other solution. This isn't worth the technical debt (another term they'd learn if they got a degree in Software Engineering). I've already lost 3 work days on this when I could have be implementing something else.
They clearly don't give a shit about us customers so why should we stay? I'll be talking with my product manager today about getting off this platform.
01-17-2019 06:47 AM - edited 01-17-2019 06:52 AM
I finally got the hash to match the "x_sha2_hash" value from the relay response. But if I try to use the same C# method provide from https://developer.authorize.net/support/hash_upgrade/ to compute the hash for "x_fp_hash", I always get a response saying the transaction cannot be accepted.
So far, if I pass a md5 hash for x_fp_hash when creating a payment and on the relay response I compute the hash with sha512 and compare with x_sha2_hash, everything works.
The only question is does the removal of md5 hash also means the x_fp_hash needs to be using sha512?
01-17-2019 08:40 AM - edited 01-17-2019 08:41 AM
Would you mind telling us how you got it to match? Which hash did you use? Which encoding? Thanks!
01-17-2019 08:53 AM
This is the C# code, I added a check for the lenght of the key so that it would not break existing integration.
var computedHash = string.Empty; if (key.Length == 128) { var textToHash = string.Join("^", request.Params["x_trans_id"], request.Params["x_test_request"], request.Params["x_response_code"], request.Params["x_auth_code"], request.Params["x_cvv2_resp_code"], request.Params["x_cavv_response"], request.Params["x_avs_code"], request.Params["x_method"], request.Params["x_account_number"], request.Params["x_amount"], request.Params["x_company"], request.Params["x_first_name"], request.Params["x_last_name"], request.Params["x_address"], request.Params["x_city"], request.Params["x_state"], request.Params["x_zip"], request.Params["x_country"], request.Params["x_phone"], request.Params["x_fax"], request.Params["x_email"], request.Params["x_ship_to_company"], request.Params["x_ship_to_first_name"], request.Params["x_ship_to_last_name"], request.Params["x_ship_to_address"], request.Params["x_ship_to_city"], request.Params["x_ship_to_state"], request.Params["x_ship_to_zip"], request.Params["x_ship_to_country"], request.Params["x_invoice_num"] ); computedHash = HMACSHA512(key, $"^{textToHash}^"); var sha2 = request.Params["x_SHA2_Hash"].ToString(); return sha2 == computedHash; } else { var md5er = MD5.Create(); var bytes = System.Text.Encoding.ASCII.GetBytes(key + loginId + transactionId + amount); computedHash = String.Join("", md5er.ComputeHash(bytes).Select(s => s.ToString("X2"))); return md5 == computedHash; }
and the hashing method is what was provided from https://developer.authorize.net/support/hash_upgrade/
public static string HMACSHA512(string key, string textToHash) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("HMACSHA512: key", "Parameter cannot be empty."); if (string.IsNullOrEmpty(textToHash)) throw new ArgumentNullException("HMACSHA512: textToHash", "Parameter cannot be empty."); if (key.Length % 2 != 0 || key.Trim().Length < 2) { throw new ArgumentNullException("HMACSHA512: key", "Parameter cannot be odd or less than 2 characters."); } try { byte[] k = Enumerable.Range(0, key.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(key.Substring(x, 2), 16)) .ToArray(); HMACSHA512 hmac = new HMACSHA512(k); byte[] HashedValue = hmac.ComputeHash((new System.Text.ASCIIEncoding()).GetBytes(textToHash)); return BitConverter.ToString(HashedValue).Replace("-", string.Empty); } catch (Exception ex) { throw new Exception("HMACSHA512: " + ex.Message); } }
But using the same hashing method to create the hash for "x_fp_hash" got a "transaction cannot be accepted" response.
01-17-2019 09:33 AM
01-17-2019 09:51 AM
This may seem odd but can you provide all the test data that you used so I can place the same data in my implementation and see if what I coded is correct and has your output? I'm using Java libraries for the hashing so I assume they are correct since they worked for the original MD5 but maybe it's handling something wrong.
Like give me the concatenated hash, the key, and the output.
Again, this surprises me that in the guide they don't provide input and output in their examples. Only input. Which is pretty useless.
01-17-2019 10:42 AM - edited 01-17-2019 10:43 AM
using the following key and text should produce this x_SHA2_Hash. Hope this help.
key "9C5A4D2AFE1D1D5DB3A8FC4C95CDCF49E2B052B4220D0624C54C1C662194BDEF8FE0EA27B313FA62328D9500D123B9DD3CE06644508803ACD04DAEDB24C5D122"
text = "^0^false^3^^^^P^^^1.99^^^^^^^^^^^^^^^^^^^^^"
x_SHA2_Hash= "4038397293CE5DBBFD5AD21B2F96FF5B6E32EA9F56D310AA95D1D091EBEAD1C35FE5AF96111D7911B2816FCE0DBA2D41A5BD268F766183DC8F3B750B22F68E16"
01-17-2019 11:32 AM
01-17-2019 11:39 AM
Finally got the x_fp_hash as well. It turns out the x_fp_timestamp I was using in my test was too old, it has to be with in 15 mins difference from authorize's server. After I updated my code to get the current time it worked.
01-17-2019 11:55 AM
This is my c# test code incase anyone is interested
var epoTime = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; const string amount = "1.99", x_fp_sequence = "2627"; var x_fp_timestamp = epoTime.ToString(); string textToHash = $"{loginId}^{x_fp_sequence}^{x_fp_timestamp}^{amount}^USD"; var x_fp_hash = HMACSHA512(key, textToHash);
01-17-2019 12:00 PM