Sage Pay v3.00 Integration

2020-02-13 06:56发布

问题:

Can anyone help me incorporate the Sagepay v3.00 AES/CBC/PKCS#5 algorithm (encryption) into the following file. I'm really struggling to understand how to include so that customer data is encrypted to the new standard and then decrypted on the way back. Using Sagepay Form with a very old version of cs-cart, though have successfully managed to upgrade from version 2.22 to 2.23, but Sagepay are pulling all support from July.

Not sure how much of this script is relevant to the encryption:

<?php

if ( !defined('IN_CSCART') ) { die('Access denied'); }

if (defined('PAYMENT_NOTIFICATION')) {  

// Get the password
$payment_id=db_get_field("SELECT $db_tables[payments].payment_id FROM $db_tables[payments] LEFT JOIN $db_tables[payment_processors] ON $db_tables[payment_processors].processor_id = $db_tables[payments].processor_id WHERE $db_tables[payment_processors].processor_script='protx_form.php'");
$processor_data = fn_get_payment_method_data($payment_id);

$result = "&".simpleXor(base64Decode($_REQUEST['crypt']), $processor_data["params"]["password"])."&";

preg_match("/Status=(.+)&/U", $result, $a);
if(trim($a[1]) == "OK") {
    $pp_response['order_status'] = ($processor_data["params"]["transaction_type"] == 'PAYMENT') ? 'P' : 'O';
    preg_match("/TxAuthNo=(.+)&/U", $result, $authno);
    $pp_response["reason_text"] = "AuthNo: ".$authno[1];
    preg_match("/VPSTxID={(.+)}/U", $result, $transaction_id);
    $pp_response["transaction_id"] = @$transaction_id[1];
} else {
    $pp_response['order_status'] = 'F';
    preg_match("/StatusDetail=(.+)&/U", $result, $stat);
    $pp_response["reason_text"] = "Status: ".trim($stat[1])." (".trim($a[1]).") ";
}
preg_match("/AVSCV2=(.*)&/U", $result, $avs);
if(!empty($avs[1])) {
    $pp_response['descr_avs'] = $avs[1];
}
include $payment_files_dir.'payment_cc_complete.php';
fn_order_placement_routines($order_id);

}
else
{
    global $http_location, $b_order, $_total_back;

    $post_address = ($processor_data['params']['testmode'] != "N") ? "https://test.sagepay.com/gateway/service/vspform-register.vsp" : "https://live.sagepay.com/gateway/service/vspform-register.vsp";

    $post["VPSProtocol"] = "2.23";
    $post["TxType"] = $processor_data["params"]["transaction_type"];
    $post["Vendor"] = htmlspecialchars($processor_data["params"]["vendor"]);

    // Form Cart products
    $strings = 0;
    if (is_array($cart['products'])) {
        $strings += count($cart['products']);
    }

    if (!empty($cart['products'])) {
        foreach ($cart['products'] as $v) {
            $_product = db_get_field("SELECT product FROM $db_tables[product_descriptions] WHERE product_id='$v[product_id]' AND lang_code='$cart_language'");
            $products_string .= ":".str_replace(":", " ", $_product).":".$v['amount'].":".fn_format_price($v['subtotal']/$v['amount']).":::".fn_format_price($v['subtotal']);
        }
    }
    if (!empty($cart['payment_surcharge'])) {
        $products_string .= ":Payment surcharge:---:---:---:---:".fn_format_price($cart['payment_surcharge']);
        $strings ++;
    }
    if (!empty($cart['shipping_cost'])) {
        $products_string .= ":Shipping cost:---:---:---:---:".fn_format_price($cart['shipping_cost']);
        $strings ++;
    }
    $post_encrypted .= "Basket=".$strings.$products_string;

    $post["Crypt"] = base64_encode(simpleXor($post_encrypted, $processor_data["params"]["password"]));
    $post["Crypt"] = htmlspecialchars($post["Crypt"]);

    $msg = fn_get_lang_var('text_cc_processor_connection');
    $msg = str_replace('[processor]', 'Protx Server', $msg);

echo <<<EOT
<html>
<body onLoad="document.process.submit();">
<form action="{$post_address}" method="POST" name="process">
<INPUT type=hidden name="VPSProtocol" value="{$post['VPSProtocol']}">
<INPUT type=hidden name="Vendor" value="{$post['Vendor']}">
<INPUT type=hidden name="TxType" value="{$post['TxType']}">
<INPUT type=hidden name="Crypt" value="{$post['Crypt']}">
<p>
<div align=center>{$msg}</div>
</p>
</body>
</html>
EOT;
}

exit;

//
// ---------------- Additional functions ------------
//
function simpleXor($InString, $Key) {
$KeyList = array();
$output = "";

for($i = 0; $i < strlen($Key); $i++){
    $KeyList[$i] = ord(substr($Key, $i, 1));
}
for($i = 0; $i < strlen($InString); $i++) {
    $output.= chr(ord(substr($InString, $i, 1)) ^ ($KeyList[$i % strlen($Key)]));
}

return $output;
}

function base64Decode($scrambled) {
// Initialise output variable
$output = "";
// Fix plus to space conversion issue
$scrambled = str_replace(" ","+",$scrambled);
// Do encoding
$output = base64_decode($scrambled);
// Return the result
return $output;
}
?>

回答1:

You could try dropping the following functions into the script, then swapping out simpleXor for encryptAes. Make sure that you also add an '@' symbol as the first character of the crypt string (and strip it off when decoding the response from Sage Pay).

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, $password)
{
    $strInitVector = $password;
    $strIn = pack('H*', $hex);
    $string = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $password, $strIn, MCRYPT_MODE_CBC,$strInitVector);
    return removePKCS5Padding($string);
}


回答2:

You could try this. I can't test it, so let me know how you get on.

<?php

    if ( !defined('IN_CSCART') ) { die('Access denied'); }

    if (defined('PAYMENT_NOTIFICATION')) {  

    // Get the password
    $payment_id=db_get_field("SELECT $db_tables[payments].payment_id FROM $db_tables[payments] LEFT JOIN $db_tables[payment_processors] ON $db_tables

    [payment_processors].processor_id = $db_tables[payments].processor_id WHERE $db_tables[payment_processors].processor_script='protx_form.php'");
    $processor_data = fn_get_payment_method_data($payment_id);


    #Rik added:
    $result = "&".decryptAes($_REQUEST['crypt'], $processor_data["params"]["password"])."&";

    #$result = "&".simpleXor(base64Decode($_REQUEST['crypt']), $processor_data["params"]["password"])."&";

    preg_match("/Status=(.+)&/U", $result, $a);
    if(trim($a[1]) == "OK") {
        $pp_response['order_status'] = ($processor_data["params"]["transaction_type"] == 'PAYMENT') ? 'P' : 'O';
        preg_match("/TxAuthNo=(.+)&/U", $result, $authno);
        $pp_response["reason_text"] = "AuthNo: ".$authno[1];
        preg_match("/VPSTxID={(.+)}/U", $result, $transaction_id);
        $pp_response["transaction_id"] = @$transaction_id[1];
    } else {
        $pp_response['order_status'] = 'F';
        preg_match("/StatusDetail=(.+)&/U", $result, $stat);
        $pp_response["reason_text"] = "Status: ".trim($stat[1])." (".trim($a[1]).") ";
    }
    preg_match("/AVSCV2=(.*)&/U", $result, $avs);
    if(!empty($avs[1])) {
        $pp_response['descr_avs'] = $avs[1];
    }
    include $payment_files_dir.'payment_cc_complete.php';
    fn_order_placement_routines($order_id);

    }
    else
    {
        global $http_location, $b_order, $_total_back;

        $post_address = ($processor_data['params']['testmode'] != "N") ? "https://test.sagepay.com/gateway/service/vspform-register.vsp" : 

    "https://live.sagepay.com/gateway/service/vspform-register.vsp";

        $post["VPSProtocol"] = "2.23";
        $post["TxType"] = $processor_data["params"]["transaction_type"];
        $post["Vendor"] = htmlspecialchars($processor_data["params"]["vendor"]);

        // Form Cart products
        $strings = 0;
        if (is_array($cart['products'])) {
            $strings += count($cart['products']);
        }

        if (!empty($cart['products'])) {
            foreach ($cart['products'] as $v) {
                $_product = db_get_field("SELECT product FROM $db_tables[product_descriptions] WHERE product_id='$v[product_id]' AND lang_code='$cart_language'");
                $products_string .= ":".str_replace(":", " ", $_product).":".$v['amount'].":".fn_format_price($v['subtotal']/$v['amount']).":::".fn_format_price($v

    ['subtotal']);
            }
        }
        if (!empty($cart['payment_surcharge'])) {
            $products_string .= ":Payment surcharge:---:---:---:---:".fn_format_price($cart['payment_surcharge']);
            $strings ++;
        }
        if (!empty($cart['shipping_cost'])) {
            $products_string .= ":Shipping cost:---:---:---:---:".fn_format_price($cart['shipping_cost']);
            $strings ++;
        }
        $post_encrypted .= "Basket=".$strings.$products_string;


    #Rik added:

     $post["Crypt"] = "@".encryptAes($post_encrypted, $processor_data["params"]["password"]);

    #    $post["Crypt"] = base64_encode(simpleXor($post_encrypted, $processor_data["params"]["password"]));
    #    $post["Crypt"] = htmlspecialchars($post["Crypt"]);

        $msg = fn_get_lang_var('text_cc_processor_connection');
        $msg = str_replace('[processor]', 'Protx Server', $msg);

    echo <<<EOT
    <html>
    <body onLoad="document.process.submit();">
    <form action="{$post_address}" method="POST" name="process">
    <INPUT type=hidden name="VPSProtocol" value="{$post['VPSProtocol']}">
    <INPUT type=hidden name="Vendor" value="{$post['Vendor']}">
    <INPUT type=hidden name="TxType" value="{$post['TxType']}">
    <INPUT type=hidden name="Crypt" value="{$post['Crypt']}">
    <p>
    <div align=center>{$msg}</div>
    </p>
    </body>
    </html>
    EOT;
    }

    exit;

    //
    // ---------------- Additional functions ------------
    //
    function simpleXor($InString, $Key) {
    $KeyList = array();
    $output = "";

    for($i = 0; $i < strlen($Key); $i++){
        $KeyList[$i] = ord(substr($Key, $i, 1));
    }
    for($i = 0; $i < strlen($InString); $i++) {
        $output.= chr(ord(substr($InString, $i, 1)) ^ ($KeyList[$i % strlen($Key)]));
    }

    return $output;
    }

    function base64Decode($scrambled) {
    // Initialise output variable
    $output = "";
    // Fix plus to space conversion issue
    $scrambled = str_replace(" ","+",$scrambled);
    // Do encoding
    $output = base64_decode($scrambled);
    // Return the result
    return $output;
    }


    #added by Rik

    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, $password)
    {

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

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

    ?>


回答3:

/*First build your data. */

$data = 'variableA='.$this->variableA;
$data .= '&variableB='.$this->variableB;
...
$data .= '&variableZ='.$this->variableZ;

/** Encript data */
$dataEncrip = $this->encData($data);


/** function to Encrypt *//
public function encData($data){
  $data = $this->pkcs5_pad( $data, 16);
  $dataEnc = "@".bin2hex( mcrypt_encrypt( MCRYPT_RIJNDAEL_128,
                                          $this->passwordToEncript,
                                          $data,
                                          MCRYPT_MODE_CBC,
                                          $this->getPasswordToEncrypt()));
  return $dataEnc;                                                           
}

 /** Pkcs5_pad */
public function pkcs5_pad( $data, $blocksize ){
    $pad = $blocksize - (strlen( $data ) % $blocksize);
    return $data . str_repeat( chr( $pad ), $pad );
}