将 mcrypt 升级为 SagePay 形式的 OpenSSL 加密

Upgrade mcrypt to OpenSSL encryption in SagePay form

我有一个收集数据的表单,然后将其提交给 SagePay,传递数据。在我们需要更新到 PHP 7.2 之前,这一直运行良好,并且由于不再支持 mcrypt,我们将切换到 OpenSSL。

收集数据的表单工作正常,但传输数据未加密,交易失败。

函数文件中的代码:

$protx_encryption_password      = "my_password";
$protx_vendor                   = "my_vendor";

$data = "";
while(list($key,$value) = each($protx)) {
    $data .= $key."=".$value."&";
}
$data = trim($data," &");

$crypt = openssl_encrypt($data, $protx_encryption_password);

function openssl_encrypt($string, $key) {
    $iv = substr($value, 0, 16);
    $ciphertext = substr($value, 16);
    $key = hash('sha256', $key, true);
    $crypt = openssl_encrypt(
        $ciphertext, 'AES-256-CBC', $key, 
        OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, 
        $iv
    );
    return rtrim($crypt, "[=10=]");
}

$crypt 数据在 mcrypt 版本中像以前一样在隐藏字段中发送,我只是需要一些帮助来使加密工作。

之前在mcrypt方式中:

$crypt = encryptAes($data, $protx_encryption_password);

function encryptAes($string, $key) {
// AES encryption, CBC blocking with PKCS5 padding then HEX encoding.
// Add PKCS5 padding to the text to be encypted.
$string = 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));
}

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

这是我完成的第一个 OpenSSL 加密,因此如果能提供任何帮助,我将不胜感激。 SagePay 需要使用具有 128 位块大小的 AES-128 加密在 CBC 模式下使用 PCKS#5 (AES-128-CBC-PKCS#5) 进行加密。任何帮助都会很有帮助。

首先,这是一个糟糕的方案。使用(真实的)密码作为现代密码学的密钥,即使有可能,也几乎总是不安全的。密码和密钥是不同的东西,它们的设计和指定方式不同。当然,有些不知道自己在做什么的人 实际上是密码的密钥,因为我不知道 SagePay(当然你也不知道给出一个真实的价值)我不知道这里是否是这种情况。此外,使用密钥( 密码)作为 IV 是非常不标准的。 IV 的全部要点是对于给定密钥下的每个加密(或至少每个数据值)不同,而根据定义,给定密钥与其自身相同。这太愚蠢了,我不记得看到过任何关于它是否启用以及启用什么攻击的分析,尽管作为一般规则,违反 'security contract' 通常确实会导致问题。对 IV 使用 any 固定值是一个更常见的错误,它对 CBC 模式的影响不大,但对其他一些模式来说更糟。

但是,您显然没有更改此方案的选项,如果您这样做了,它就不会真正成为编程 Q,而是属于 security.SX 或者 crypto.SX取决于。

另外,仅供参考,说 "AES-128 with a 128-bit block" 毫无意义。 AES 总是 使用 128 位块;这是建立它的比赛规则的一部分。 Rijndael 确实有其他块大小的选项,这就是为什么或多或少实现了 Rijndael 的 mcrypt 指定了块大小,但 OpenSSL 实现了 AES,它不需要也确实不指定块大小——密钥大小。

其次,您提议的新代码是无稽之谈。首先你尝试重新定义一个标准函数,这是不允许的,然后你提出的逻辑显然被设计为解密,而不是加密,一个完全不同的方案,而不是你提出的方案,然后被破坏。

mcrypt_encrypt 零填充纯文本,在旧版本中根据需要填充密钥和 iv。对于 Rijndael,它还根据提供的密钥选择密钥大小;因为你说你想要 AES-128(即 128 位密钥),所以所谓的 'password' 不能超过 128 位,但可能更少。您发布的代码对数据进行显式 PKCS5 填充,因此不使用 mcrypt 的零填充,正如 Peter 评论的那样,openssl_encrypt(以及它在下面使用的 OpenSSL 例程)默认执行 PCKS5/7 填充,所以你只需要:

function encryptAes_new ($string, $key) {
  $key = str_pad($key,16,"[=10=]"); # if supplied key is, or may be, less than 16 bytes
  $crypt = openssl_encrypt($string, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $key);
  // Perform hex encoding and return.
  return "@" . strtoupper(bin2hex($crypt));
}

(注意:原来的PKCS5 padding只处理64-bit/8-byte块,PKCS7扩展到其他尺寸,但是2017年的PKCS5v2.1也参考CMS扩展了它,CMS是PKCS7的IETF版本,所以他们现在是一样的。)