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

@Renaissance

 

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.

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?

Would you mind telling us how you got it to match? Which hash did you use? Which encoding? Thanks!

lightwave365
Regular Contributor

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.

@karenb,

What I am saying is that those 2 values are not contradictory values for the same thing. One value is for the fingerprint and one is for the verification. The fingerprint is the code I posted second. There is another user on another thread that has code that looks identical to mine except without strtoupper and that users code is working.

All I did was pull docs for the older methods and put it together piece by piece. I don’t use anything but the newer stuff cause I’m new to this. I do think my code will work for SIM/DPM for the fingerprint.

For the verification that is something to be worked out? Because it looks possible that unless your customer submits a value for every one of those parameters every time, your hash string function will have to put together a unique set of items.

@lightwave365,

I understand. It’s different strokes for different folks I guess. With all respect, this service has worked extremely well for me and I’m doing quite the opposite. I’m steering every customer in ecommerce that I get towards this service. This is the only provider I’ve used, so I admittedly have nothing to compare to. It may be a case of ignorance is bliss, just maybe, but I see no reason not to use what works so well for me.
But yes, there is a lot to this and I can see it getting overwhelming when you have many customers all using different methods.

My check just came. Best of luck to everyone until next time.

@xero

 

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.

lightwave365
Regular Contributor

@lightwave365

 

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"

 

 

 

 

@xero,

For the fingerprint you use a different string than what is on the upgrade link. That link is for the new API and not DPM/SIM. The second php example I posted on this thread has the string for the fingerprint for those methods. I haven’t tested it but it should work. Another user has the same string and has posted that their fingerprint is working. And for your other question, as I understand it at some later date both MD5 values will be axed in favor of sha512

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.

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);