I've been trying with no avail to understand how to upgrade the encryption used in the code for my company's three ecommerce sites from simpleXor to an AES encryption. Without doing this, I cannot upgrade the rest of the code which means after July we won't be able to take any payments online.
I've managed to update the crypt form codes in line with the upgrade to 3.00.
I can identify the encryption code and have looked through the form integration demo download for PHP from Sagepay but can't find anything that looks remotely similar to my encryption code?!
Could somebody point me in the right direction for finding a suitable encryption code to replace the old one with!?
Our sites are based on JShop and I have one file for sending and a response file.
This is the file for sending info:
<?php function startProcessor($orderNumber) { global $dbA,$orderArray,$jssStoreWebDirHTTP,$jssStoreWebDirHTTPS,$cartMain; $callBack = "$jssStoreWebDirHTTPS"."gateways/response/protx.php"; $cDetails = returnCurrencyDetails($orderArray["currencyID"]); $gatewayOptions = retrieveGatewayOptions("PROTX"); switch ($gatewayOptions["testMode"]) { case "S": $myAction = "https://test.sagepay.com/Simulator/VSPFormGateway.asp"; break; case "Y": $myAction = "https://test.sagepay.com/gateway/service/vspform-register.vsp"; break; case "N": $myAction = "https://live.sagepay.com/gateway/service/vspform-register.vsp"; break; } $myVendor = $gatewayOptions["vendor"]; $myEncryptionPassword = $gatewayOptions["encryptionPassword"]; $billingAddress = $orderArray["address1"]."\n"; if ($orderArray["address2"] != "") { $billingAddress .= $orderArray["address2"]."\n"; } $billingAddress .= $orderArray["town"]."\n"; $billingAddress .= $orderArray["county"]."\n"; $billingAddress .= $orderArray["country"]; $deliveryAddress = $orderArray["deliveryAddress1"]."\n"; if ($orderArray["deliveryAddress2"] != "") { $deliveryAddress .= $orderArray["deliveryAddress2"]."\n"; } $deliveryAddress .= $orderArray["deliveryTown"]."\n"; $deliveryAddress .= $orderArray["deliveryCounty"]."\n"; $deliveryAddress .= $orderArray["deliveryCountry"]; $crypt = "VendorTxCode=$orderNumber";
$crypt .= "&Amount=".number_format($orderArray["orderTotal"],$cDetails["decimals"],'.','');
$crypt .= "&Currency=".@$cDetails["code"];
$crypt .= "&Description=".$gatewayOptions["description"];
$crypt .= "&SuccessURL=$callBack?xOid=$orderNumber&xRn=".$orderArray["randID"];
$crypt .= "&FailureURL=$callBack?xOid=$orderNumber&xRn=".$orderArray["randID"];
$crypt .= "&BillingSurname=".$orderArray["surname"];
$crypt .= "&BillingFirstnames=".$orderArray["forename"];
$crypt .= "&BillingAddress1=".$orderArray["address1"];
$crypt .= "&BillingCity=".$orderArray["town"];
$crypt .= "&BillingPostCode=".preg_replace("/[^\s\-a-zA-Z0-9]/", "", $orderArray["postcode"]);
$crypt .= "&BillingCountry=".$orderArray["country"];
$crypt .= "&DeliverySurname=".&orderArray["surname"];
$crypt .= "&DeliveryFirstnames=".&orderArray["forename"];
if ($orderArray["deliveryPostcode"] != "") {
$crypt .= "&DeliveryAddress1=".$orderArray["deliveryAddress1"];
$crypt .= "&DeliveryCity=".$orderArray["deliveryTown"];
$crypt .= "&DeliveryPostCode=".preg_replace("/[^\s\-a-zA-Z0-9]/", "", $orderArray["deliveryPostcode"]);
$crypt .= "&DeliveryCountry=".$orderArray["deliveryCountry"]; }
else {
$crypt .= "&DeliveryAddress1=".$orderArray["address1"];
$crypt .= "&DeliveryCity=".$orderArray["town"];
$crypt .= "&DeliveryPostCode=".preg_replace("/[^\s\-a-zA-Z0-9]/", "", $orderArray["postcode"]);
$crypt .= "&DeliveryCountry=".$orderArray["country"]; }
$crypt .= "&BillingPhone=".preg_replace("/[^\sa-zA-Z0-9]/", "", $orderArray["telephone"]);
if ($gatewayOptions["sendEmail"] == 1) {
$crypt .= "&CustomerEmail=".$orderArray["email"]; }
$crypt .= "&VendorEmail=".$gatewayOptions["vendorEmail"];
$crypt .= "&ApplyAVSCV2=".$gatewayOptions["cvvCheck"];
$crypt .= "&Apply3DSecure=".$gatewayOptions["3DSecure"];
$crypt = base64_encode(protx_simpleXor($crypt,$myEncryptionPassword));
$tpl = createTSysObject(templatesCreatePath($cartMain["templateSet"]),"gatewaytransfer.html",$requiredVars,0);
$gArray["method"] = "POST";
$gArray["action"] = $myAction;
$gArray["fields"][] = array("name"=>"VPSProtocol","value"=>"3.00");
$gArray["fields"][] = array("name"=>"Vendor","value"=>$myVendor);
$gArray["fields"][] = array("name"=>"TxType","value"=>$gatewayOptions["txType"]); $gArray["fields"][] = array("name"=>"Crypt","value"=>$crypt);
$mArray = $gArray;
$gArray["process"] = "document.automaticForm.submit();";
$tpl->addVariable("shop",templateVarsShopRetrieve());
$tpl->addVariable("labels",templateVarsLabelsRetrieve());
$tpl->addVariable("automaticForm",$gArray);
$tpl->addVariable("manualForm",$mArray);
$tpl->showPage(); }
function protx_simpleXor($inString, $key) { $outString=""; $l=0; if (strlen($inString)!=0) { for ($i = 0; $i < strlen($inString); $i++) { $outString=$outString . ($inString[$i]^$key[$l]); $l++; if ($l==strlen($key)) { $l=0; } } } return $outString; } ?>
This is the response file:
<?php
/*================ JShop Server ================
= (c)2003-2010 Whorl Ltd. =
= All Rights Reserved =
= Redistribution of this file is prohibited. =
= http://www.jshop.co.uk/ =
==============================================*/
?><?php
define("IN_JSHOP", TRUE);
include("../../static/config.php");
include("../../routines/dbAccess_".$databaseType.".php");
include("../../routines/tSys.php");
include("../../routines/general.php");
include("../../routines/stockControl.php");
include("../../routines/emailOutput.php");
dbConnect($dbA);
$orderID = makeSafe(getFORM("xOid"));
$newOrderID = $orderID;
$randID = makeSafe(getFORM("xRn"));
$crypt = makeSafe(getFORM("crypt"));
$gatewayOptions = retrieveGatewayOptions("PROTX");
$orderID = makeInteger($orderID) - retrieveOption("orderNumberOffset");
$result = $dbA->query("select * from $tableOrdersHeaders where orderID=$orderID and randID='$randID'");
if ($dbA->count($result) == 0 || $crypt=="") {
doRedirect_JavaScript($jssStoreWebDirHTTP."index.php");
exit;
}
$orderArray = $dbA->fetch($result);
$ccResult = $dbA->query("select * from $tablePaymentOptions where paymentID=".$orderArray["paymentID"]);
$poRecord = $dbA->fetch($ccResult);
$paidStatus = $poRecord["statusID"];
$crypt = str_replace(" ","+",$crypt);
$crypt = protx_simpleXor(base64_decode($crypt),$gatewayOptions["encryptionPassword"]);
$nameValues = explode("&",$crypt);
$resultCode = "";
for ($f = 0; $f < count($nameValues); $f++) {
$thisCode = explode("=",$nameValues[$f]);
$resultCode[$thisCode[0]] = $thisCode[1];
}
if ($resultCode["VendorTxCode"] != $newOrderID) {
doRedirect_JavaScript($jssStoreWebDirHTTP."index.php");
exit;
}
$authResponse = "&Status Result=".$resultCode["Status"]."&AVS/CV2 Check=".@$resultCode["AVSCV2"]."&Address Result=".@$resultCode["AddressResult"]."&Postcode Result=".@$resultCode["PostCodeResult"]."&CV2 Result=".@$resultCode["CV2Result"]."&3d Secure Status=".@$resultCode["3DSecureStatus"];
$randID = $orderArray["randID"];
if ($orderArray["status"] != $paidStatus) {
$dt=date("YmdHis",createOffsetTime());
switch ($resultCode["Status"]) {
case "OK":
case "AUTHENTICATED":
case "REGISTERED":
$authResponse="Gateway=Sage Pay&Authorisation Code=".$resultCode["TxAuthNo"]."&Sage Pay Transaction ID=".$resultCode["VPSTxId"]."&Status=Payment Confirmed".$authResponse;
$dbA->query("update $tableOrdersHeaders set status=$paidStatus, authInfo=\"$authResponse\", paymentDate=\"$dt\" where orderID=$orderID");
$orderArray["status"] = $paidStatus;
//ok, this is where we should do the stock control then.
include("process/paidProcessList.php");
doRedirect_JavaScript($jssStoreWebDirHTTPS."process.php?xOid=$newOrderID&xRn=$randID");
break;
case "REJECTED":
$authResponse="Gateway=Sage Pay&Status=Payment Rejected Due To Rules".$authResponse;
$dbA->query("update $tableOrdersHeaders set status=3, authInfo=\"$authResponse\", paymentDate=\"$dt\" where orderID=$orderID");
include("process/failProcessList.php");
doRedirect_JavaScript($jssStoreWebDirHTTPS."process.php?xOid=$newOrderID&xRn=$randID");
break;
default:
if ($orderArray["status"] == 1) {
$authResponse="Gateway=Sage Pay&Status=Payment Failed".$authResponse;
$dbA->query("update $tableOrdersHeaders set status=3, authInfo=\"$authResponse\", paymentDate=\"$dt\" where orderID=$orderID");
include("process/failProcessList.php");
}
doRedirect_JavaScript($jssStoreWebDirHTTPS."process.php?xOid=$newOrderID&xRn=$randID");
break;
}
} else {
doRedirect_JavaScript($jssStoreWebDirHTTPS."process.php?xOid=$newOrderID&xRn=$randID");
}
function protx_simpleXor($inString, $key) {
$outString="";
$l=0;
if (strlen($inString)!=0) {
for ($i = 0; $i < strlen($inString); $i++) {
$outString=$outString . ($inString[$i]^$key[$l]);
$l++;
if ($l==strlen($key)) { $l=0; }
}
}
return $outString;
}
?>
It looks like you are using very old code. I would recommend you try to completely port/rewrite your code using the official Sage Pay integration libraries to avoid strange bugs with things like escaping.
If you're in a rush, here is a stripped down version of the class you need to get the encryption/decryption part working.
Setup your variables as you do now.
Then for encryption (sending to sagepay) use this
And for decryption (receiving from sagepay) use this
Edit: Removed the base64encoding layer. Protx will assume a base64 encoded string is using the old XOR method so you must not use base64 encoding. Instead it is necessary to use hex encoding and start the string with the "@" character.