Search code examples
phpencryptionupgradeopayo

Sagepay error 5068: encryption method not supported (Upgrade from 2.22 to 3.00)


I've already looked through answers to other questions on the Sagepay protocol upgrade from 2.22 to 3.00, including the answer on my own previous question.

I am trying to update the encryption method from Xor to AES so the 3.00 protocol will work but have thus far got stuck on the Sagepay error 5068: the encryption method is not supported by this protocol. My company's three websites that need to be updated are based on the JShop e-commerce platform which is very outdated and no longer offers support.

I have implemented two solutions given as answers, one of which is taken from the Sagepay php documents available on their website but am still receiving this error.

I'd appreciate some input into where I am going wrong.

Have contacted Sagepay but they're not much help and have previously directed me here or to their partners page.

Protx (Sagepay) file:

    <?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(encryptAes($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 addPKCS5Padding($input)
    {
         $blockSize = 16;
         $padd = "";
         $length = $blockSize - (strlen($input) % $blockSize);
         for ($i = 1; $i <= $length; $i++)
    {
         $padd .= chr($length);
    }
         return $input . $padd;
    }

    function removePKCS5Padding($input)
    {
        $blockSize = 16;
        $padChar = ord($input[strlen($input) - 1]);
        $unpadded = substr($input, 0, (-1) * $padChar);
        return $unpadded;
    }


    function encryptAes($string, $key)
    {
        $string = addPKCS5Padding($string);
        $crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $key);
        return  strtoupper(bin2hex($crypt));
    }


    function decryptAes($strIn, $myEncryptionPassword)
    {

    #Sagepay specific - remove the '@'
    $strIn = substr($strIn,1);

        $strInitVector = $myEncryptionPassword;
        $strIn = pack('H*', $hex);
        $string = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $myEncryptionPassword, $strIn, MCRYPT_MODE_CBC,$strInitVector);
        return removePKCS5Padding($string);
    }

?>

Response file:

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

    require_once 'SagepayUtil.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"]);

    $crypt = decryptAes(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 addPKCS5Padding($input)
    {
         $blockSize = 16;
         $padd = "";
         $length = $blockSize - (strlen($input) % $blockSize);
         for ($i = 1; $i <= $length; $i++)
    {
         $padd .= chr($length);
    }
         return $input . $padd;
    }

    function removePKCS5Padding($input)
    {
        $blockSize = 16;
        $padChar = ord($input[strlen($input) - 1]);
        $unpadded = substr($input, 0, (-1) * $padChar);
        return $unpadded;
    }


    function encryptAes($string, $key)
    {
        $string = addPKCS5Padding($string);
        $crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $key);
        return  strtoupper(bin2hex($crypt));
    }


    function decryptAes($strIn, $myEncryptionPassword)
    {

    #Sagepay specific - remove the '@'
    $strIn = substr($strIn,1);

        $strInitVector = $myEncryptionPassword;
        $strIn = pack('H*', $hex);
        $string = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $myEncryptionPassword, $strIn, MCRYPT_MODE_CBC,$strInitVector);
        return removePKCS5Padding($string);
    }

?>

Solution

  • Make sure you back up the files first, then change this line:

    $crypt =base64_encode(encryptAes($crypt, $myEncryptionPassword));
    

    to

    $crypt = "@".encryptAes($crypt, $myEncryptionPassword);
    

    And:

    $crypt = decryptAes(base64_decode($crypt), $gatewayOptions["encryptionPassword"]);
    

    To:

    $crypt = decryptAes($crypt, $gatewayOptions["encryptionPassword"]);
    

    Having looked at the decrypt bit, the removal of the '@' is already there. I'm not a PHP guy, so this may not be perfect!