我的 php 脚本无法解密 Sagepay Form 版本 3.00 中的 return crypt

My php script does not decrypt return crypt in Sagepay Form version 3.00

我已将我的网站移至新的托管服务提供商,我的 Sagepay Form v3 脚本接收加密响应现在失败了。

在以前的托管服务提供商处,脚本可以正常工作(php 版本是 5.5.9),新托管服务提供 5.4 到 6 的选择。在第一个托管服务提供商处,php很久以前的版本是 5.2(或者可能是 5.3),当他们最终强制更改为 5.5 时,它破坏了我网站脚本中的很多东西,导致我在修复它们时遇到了非常困难的时期,我在结束。

其中一件事是解密失败,就像现在又一次一样。在那种情况下,我最终通过更改解密行来修复它:

$Decoded = DHclassInFunc::decryptAes($crypt,$EncryptionPassword);

至:

$Decoded = DHclassInFunc::decryptAes(($_GET['crypt']),$EncryptionPassword);

我尝试了很多其他变体,但只有最后一个变体有效。

所以现在问题又回来了,我完全不知所措。我已经尝试了所有以前的变体,但没有任何效果。还有我的新主机提供的各种 php 版本。

我之前的(LONG)问题也贴在这里:见

谁能告诉我为什么这一次失败了,我能做些什么来解决它?

编辑 14/12/18 调查后的更多信息加上我包括更多解释和两个相关脚本的完整代码 ---------- --------------

我没有取得任何进展,并且在 Sagepay return 无法正常工作时不得不手动管理网站订单。现在我有一点时间所以我再试一次。

我现在发现,如果我删除 "completed.php" 页面(Sagepay 响应指向的 url)上的这一行(下方),脚本不会挂起;然而,这是因为正是那一行导致了致命错误。

$Decoded = DHclassInFunc::decryptAes(($_GET['crypt']),$EncryptionPassword);

没有该行和由此产生的错误,scipt 能够继续并调用以下页面 ("return.php"),该页面随后向客户显示支付结果信息并执行其他操作(例如发送完整的订单详细信息到我们的本地 - 而不是在互联网上 - 数据库。

然而,删除行后,url 中的 crypt 未被处理,因此 completed.php 页面转发到 return.php 页面的结果变量中没有值。

这意味着$status变量为空;在 return.php 页面中,这被评估为错误,因此会向客户显示一条消息,指出存在错误并且没有付款 - 这是不正确的。

缺少 "success" 状态值也意味着网络 mysql 数据库中的订单未标记为已确认。

我尝试了该行的许多其他变体都无济于事(尽管此处给出的变体在网站移至新主机之前有效)。

该行当然会调用位于 functions.php 文件中的 class "DHclassInFunc" 中的函数。

我在下方附上了两个文件 completed.php 和 functions.php

的活动代码

据我所知,根本问题是

$Decoded = DHclassInFunc::decryptAes(($_GET['crypt']),$EncryptionPassword);

在 'crypt' 中没有收到任何值,因此没有字符串供 function.php 解密例程处理,导致调用该函数时出现致命错误:"PHP Fatal error: Class 'SagepayApiException' not found in /redacted/redacted/redacted.com/www/redacted/protx/functions.php on line 208"

我在functions.php代码中添加了一行如下:

echo '$strIn' . "  string in with @ should be here?";

为了尝试公开传递给函数的值,但它简单地打印了 var 的名称,而不是 url 地址栏中的内容的值 completed.php 页面,当它收到来自 Sagepay 的响应时 - 例如:

https://www.redacted.com/redacted/protx/completed.php?crypt=@ad6721a09c786829cd839586df0fe047ea0f0e9c791ddfe5d55b7175881aa4609ccfb4768a8b84dd9f259614d0edf0f03254a1967279693509e72190c8248cd56d1cefa713592f84eca4e8d7477ac89c9dd783b350a21766500c1c91fde3dbe5deb7887bea0e5c07e58274dec93224729f265730a4aecf5cf9c7216dad2b5eecc4d128e6c8389c1c9d5d297b7a10ccb53e37eae5b7a996a308c10f2d0edc0b41b6b38c6e56375a6421d110a0a3fe40cdfa2daa2fa6e0bf767204d209aa300d9f907ea686ee9a9dcc0992c14c325123ab53d7885bc6dc66eebf3c341002034fbce6277ccc6fbb8734c3cdab58dcd294d0a3a4430c7b091beed81fd97cadbf24b9149f9541e5d8e8c45a4e267fc0d14222c45963fe847ec12a9fedf05eba2a78caf769825046584b112d353d92d38aedc3cb086fc0c8250e20ef975dc377438b7c3a34c96cacba9ed1670b2af1bcd0945a5a0424c0532f23b0a6662db8198a2368d60ee3785f07826005593292154abe06abf55ff1d461b714e1fb53b5da3db1f21eb6b01169a2cf78d872de5ac96e41e088a7bf1e6f88aa8cc5c6b4bfd5d82f63

关于它是否可能是一个 unicode / iso 问题,我不明白为什么这会导致 $strIn 中的空值,因为它根本没有被处理,只是被捕获(或没有被捕获?)。

COMPLETED.php --------------------

<?php
include "functions.php";

$Decoded = DHclassInFunc::decryptAes($_GET['crypt'],$EncryptionPassword); 

$values = getToken($Decoded);
$VendorTxCode = $values['VendorTxCode'];
$Status = $values['Status'];
$VPSTxID = $values['VPSTxId'];
$TxAuthNo = $values['TxAuthNo'];
$AVSCV2 = $values['AVSCV2'];
$Amount = $values['Amount'];
// protocol 2.22 fields
$AddressResult = $values[ 'AddressResult' ];
$PostCodeResult = $values[ 'PostCodeResult' ];
$CV2Result = $values[ 'CV2Result' ];
$GiftAid = $values[ 'GiftAid' ];
$VBVSecureStatus = $values[ '3DSecureStatus' ];
$CAVV = $values[ 'CAVV' ];

// DH my all-in-one details var

$ResultDetails = $ResultDetails . "Vendor Code: " . $VendorTxCode . " - "; 
$ResultDetails = $ResultDetails . "Status: " . $Status . " - "; 
$ResultDetails = $ResultDetails . "VPS Transaction ID: " . $VPSTxID . " - ";                                                                                                                                                                                                         
$ResultDetails = $ResultDetails . "Auth Num: " . $TxAuthNo . " - "; 
$ResultDetails = $ResultDetails . "AVS / CV2 response: " . $TxAuthNo . " - "; 
$ResultDetails = $ResultDetails . "Amount: " . $Amount . " - ";         
$ResultDetails = $ResultDetails . "Address Result: " . $AddressResult . " - ";  
$ResultDetails = $ResultDetails . "PostCode Result: " . $PostCodeResult . " - ";   
$ResultDetails = $ResultDetails . "PostCode Result: " . $PostCodeResult . " - ";    
$ResultDetails = $ResultDetails . "CV2 Result: " . $CV2Result . " - ";  
$ResultDetails = $ResultDetails . "GiftAid Result: " . $GiftAid . " - ";     
$ResultDetails = $ResultDetails . "3DSecure Status: " . $VBVSecureStatus . " - "; 
$ResultDetails = $ResultDetails . "CAVV Result: " . $CAVV . " - ";  

$FindHyphen = strpos($VendorTxCode,'-');
$LastIdChar = $FindHyphen;
$MyOrderID = substr($VendorTxCode,0,$LastIdChar);

$StatusSave = $Status;

echo '  <FORM METHOD="POST" FORM NAME="GoToReturn" ACTION="../MXKart/return.php">'."\n";

echo ' <input type="hidden" name="response_code" value= "';
echo $Status;
echo '">'."\n";
echo ' <input type="hidden" name="order_number" value= "';
echo $MyOrderID;
echo '">'."\n";
echo ' <input type="hidden" name="secretword" value= "';
echo $secret_word;
echo '">'."\n";

//echo addslashes($ResultDetails);
echo ' <input type="hidden" name="response_reason_text" value= "';
echo $ResultDetails;
echo '">'."\n";
echo ' <input type="hidden" name="amount" value= "';
echo $Amount;
echo '">'."\n";
echo ' <input type="hidden" name="force" value= "';
echo $VendorTxCode;
echo '">'."\n";
$msg = "<br><strong>Getting payment result.... </strong> <br><br><h2 style=\"color:green;\">PLEASE WAIT AT THIS PAGE - do not close the page or move on. <br>There can be a delay of up to a minute so please be patient.</h2>";
echo $msg."\n"; 
    echo '</FORM>'."\n";

echo '<script language="javascript">'."\n";
echo 'document.forms[0].submit();'."\n";
echo '</script>'."\n";
?>

FUNCTIONS.php--------------------

<?

$VendorName="redacted";

$EncryptionPassword="redacted"; //   LIVE  server destination

//************ NEW CRYPT STUFF COPIED FRON SAGEPAY KIT util.php
//DH added class definition as shown in Whosebug page - trying to fix error when run, on line static private function etc
class DHclassInFunc{
/**
* PHP's mcrypt does not have built in PKCS5 Padding, so we use this.
*
* @param string $input The input string.
*
* @return string The string with padding.
*/

static protected function addPKCS5Padding($input)
{
$blockSize = 16;
$padd = "";

// Pad input to an even block size boundary.
$length = $blockSize - (strlen($input) % $blockSize);
for ($i = 1; $i <= $length; $i++)
{
$padd .= chr($length);
}

return $input . $padd;
}


/**
* Remove PKCS5 Padding from a string.
*
* @param string $input The decrypted string.
*
* @return string String without the padding.
* @throws SagepayApiException
*/
static protected function removePKCS5Padding($input)
{
$blockSize = 16;
$padChar = ord($input[strlen($input) - 1]);

/* Check for PadChar is less then Block size */
if ($padChar > $blockSize)
{
throw new SagepayApiException('Invalid encryption string');
}
/* Check by padding by character mask */
if (strspn($input, chr($padChar), strlen($input) - $padChar) != $padChar)
{
throw new SagepayApiException('Invalid encryption string');
}

$unpadded = substr($input, 0, (-1) * $padChar);
/* Chech result for printable characters */
if (preg_match('/[[:^print:]]/', $unpadded))
{
throw new SagepayApiException('Invalid encryption string');
}
return $unpadded;
}


/**
* Encrypt a string ready to send to SagePay using encryption key.
*
* @param  string  $string  The unencrypyted string.
* @param  string  $key     The encryption key.
*
* @return string The encrypted string.
*/
static public function encryptAes($string, $key)
{
// AES encryption, CBC blocking with PKCS5 padding then HEX encoding.
// Add PKCS5 padding to the text to be encypted.
$string = self::addPKCS5Padding($string);

// Perform encryption with PHP's MCRYPT module.
$crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $key);

// Perform hex encoding and return.
return "@" . strtoupper(bin2hex($crypt));
}

/**
* Decode a returned string from SagePay.
*
* @param string $strIn         The encrypted String.
* @param string $password      The encyption password used to encrypt the string.
*
* @return string The unecrypted string.
* @throws SagepayApiException
*/

static public function decryptAes($strIn, $password)

{
echo '$strIn' . "  string in with @ should be here?";

$strIn = htmlspecialchars($strIn, ENT_COMPAT,'utf-8', true);

// HEX decoding then AES decryption, CBC blocking with PKCS5 padding.
// Use initialization vector (IV) set from $str_encryption_password.
$strInitVector = $password;

// Remove the first char which is @ to flag this is AES encrypted and HEX decoding.
$hex = substr($strIn, 1);

// Throw exception if string is malformed
if (!preg_match('/^[0-9a-fA-F]+$/', $hex))
{
//DH added section to print result of decryption onto page for debugging
//$hex = "pseudo hex";
//echo "throw error at line 188";
// echo $hex;

throw new SagepayApiException('Invalid encryption string');
}
$strIn = pack('H*', $hex);

// Perform decryption with PHP's MCRYPT module.
$stringReturn = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $password, $strIn, MCRYPT_MODE_CBC, $strInitVector);
return self::removePKCS5Padding($string);
}

}

/* The getToken function.                                                                                         **
** NOTE: A function of convenience that extracts the value from the "name=value&name2=value2..." VSP reply string **
**     Works even if one of the values is a URL containing the & or = signs.                                      */


function getToken($thisString) {
// List the possible tokens
$Tokens = array(
"Status",
"StatusDetail",
"VendorTxCode",
"VPSTxId",
"TxAuthNo",
"Amount",
"AVSCV2", 
"AddressResult", 
"PostCodeResult", 
"CV2Result", 
"GiftAid", 
"3DSecureStatus", 
"CAVV" );

// Initialise arrays
$output = array();
$resultArray = array();

// Get the next token in the sequence
for ($i = count($Tokens)-1; $i >= 0 ; $i--){
// Find the position in the string
$start = strpos($thisString, $Tokens[$i]);
// If it's present
if ($start !== false){
// Record position and token name
$resultArray[$i]->start = $start;
$resultArray[$i]->token = $Tokens[$i];
}
}

// Sort in order of position
sort($resultArray);

// Go through the result array, getting the token values
for ($i = 0; $i<count($resultArray); $i++){
// Get the start point of the value
$valueStart = $resultArray[$i]->start + strlen($resultArray[$i]->token) + 1;
// Get the length of the value
if ($i==(count($resultArray)-1)) {
$output[$resultArray[$i]->token] = substr($thisString, $valueStart);
} else {
$valueLength = $resultArray[$i+1]->start - $resultArray[$i]->start - strlen($resultArray[$i]->token) - 2;
$output[$resultArray[$i]->token] = substr($thisString, $valueStart, $valueLength);
}      

}

// Return the ouput array
return $output;

}

// Randomise based on time
function randomise() {
list($usec, $sec) = explode(' ', microtime());
return (float) $sec + ((float) $usec * 100000);
}


?>

我非常需要帮助来解决代码方面的问题,或者我是否在尝试公开看似空字符串的值时犯了错误,因此得出了错误的结论。

编辑星期二 18/12/18 --------------

我已经取得了一些进展,因为我发现了页面 "completed.php" 中的 $_GET 根本没有从 [=159= 中的页面 url 获取任何值的原因] Sagepay 发送的回复。

这是因为托管平台的默认php服务器设置只接受url中最多512个字符;我能够将其更改为 2000 个字符(见后面的评论),这解决了部分问题;致命错误消失了,但解密仍然失败。但是我现在可以调试了,因为这些函数现在有数据可以使用,而且我可以跟踪脚本不同部分的值。

不幸的是我现在完全迷路了s 了解调试输出 - 因为尽管寻求帮助,但首先我根本不了解解密功能。

就解密行而言,输出似乎是合理的

$hex = substr($strIn, 1);

in "functions.php" 在“@”被剥离后产生传入密码的内容。

但是一旦脚本移动到行

$strIn = pack('H*', $hex);

它出错了,因为如果变量内容现在的输出散落着 'garbage' 个字符。我不明白 'pack' 是如何工作的,但我认为字符应该都保持可读,因此这是一个编码问题。

Link to image of a screenshot of the characters

在上面链接的图像中显示为黑色菱形内问号的字符似乎是其中的一些 -

e? g!xh)̓G]/|CՖ'#]Ws͝Y?Ig@uQ*@KѦ

当我快速捕获文本时 select-and-copy 然后将其传递到文本编辑器中。

但我不知道垃圾字符是否仅限于由 'pack' 函数插入的字符,因此编码不匹配仅限于函数,而不是整体编码问题Sagepay 的提交和 return 数据。

不幸的是,自从网站迁移到新主机、更改脚本、headers、显式编码语句、脚本文件编码、php.ini 编码声明,Mysql 数据库编码等,从旧的(大部分)ISO 到 utf_8。主要是试图摆脱网站上用户实际可见的异常字符。挤压一个,你会得到一个不同的。

所以现在我一想到如果这纯粹是一个编码问题,就想知道如何处理这个问题,我的脑袋就在颤抖。 Sagepay 告诉我 Form 3 与 unicode 兼容,但我知道这与我收到的其他建议相矛盾,事实上我自己通过之前的 php 和我的旧托管服务提供商的 sagepay 版本更改。

网站和数据库基础不可能改回 ISO,但如果是这种情况,我必须以某种方式让 Sagepay 单独使用 ISO,我怎样才能最轻松地做到这一点 - 什么是必需品吗?

提交到 Sagepay 在 utf-8 下工作得很好,但我是否必须更改它以在 ISO 中提交,然后才能为 return 指定 ISO,这才是真正的问题所在?以及如何最好地强制这个 ISO - 只是为了 Sagepay - 考虑到编码通常似乎不是 'stick',影响编码的网络技术战场。

另一方面,如果只是 'pack' 功能失调就好了;以及是否有简单的方法或地方可以解决该问题。谁能指点一下。


在 Omnipay Sage Pay 驱动程序中实施(Omnipay Common v3.x)https://github.com/thephpleague/omnipay-sagepay/blob/master/src/Message/Form/CompleteAuthorizeRequest.php#L47

$crypt = $_GET['crypt'];

// Remove the leading '@' and decrypt the remainder into a query string.

$hexString = substr($crypt, 1);

// Last minute check to make sure we have data that looks sensible.

if (! preg_match('/^[0-9a-f]+$/i', $hexString)) {
    throw new \Exception('Invalid "crypt" parameter; not hexadecimal');
}

// Decrypt the crypt string.

$queryString = openssl_decrypt(
    hex2bin($hexString),
    'aes-128-cbc',
    $yourEncryptionKey,
    OPENSSL_RAW_DATA,
    $yourEncryptionKey
);

// Parse ...&VPSTxId={AE43BAA6-52FF-0C30-635B-2D5E13B75ACE}&...
// into an array of values.

parse_str($queryString, $data);

var_dump($data);

/*
array(17) {
  ["VendorTxCode"]=>
  string(19) "your-original-unique-id"
  ["VPSTxId"]=>
  string(38) "{AE43BAA6-52FF-0C30-635B-2D5E13B75ACE}"
  ["Status"]=>
  string(2) "OK"
  ["StatusDetail"]=>
  string(40) "0000 : The Authorisation was Successful."
  ["TxAuthNo"]=>
  string(6) "376048"
  ["AVSCV2"]=>
  string(24) "SECURITY CODE MATCH ONLY"
  ["AddressResult"]=>
  string(10) "NOTMATCHED"
  ["PostCodeResult"]=>
  string(10) "NOTMATCHED"
  ["CV2Result"]=>
  string(7) "MATCHED"
  ["GiftAid"]=>
  string(1) "0"
  ["3DSecureStatus"]=>
  string(10) "NOTCHECKED"
  ["CardType"]=>
  string(4) "VISA"
  ["Last4Digits"]=>
  string(4) "0006"
  ["DeclineCode"]=>
  string(2) "00"
  ["ExpiryDate"]=>
  string(4) "1220"
  ["Amount"]=>
  string(5) "99.99"
  ["BankAuthCode"]=>
  string(6) "999777"
}
*/

PHP 7 不再支持官方 Sage Pay 库(以及许多基于该旧代码的插件)使用的旧 encryption/decryption 函数。请改用 openssl 函数。

$data 中 return 编辑的所有内容都将是 ASCII(它将仅 return 明确定义的 ID 和代码,没有用户输入的数据)。我认为它不会包含任何扩展的 ASCII 字符,因此如果需要,可以将其视为 UTF-8 而无需任何转换。