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

Posted

技术标签:

【中文标题】以 SagePay 形式将 mcrypt 升级为 OpenSSL 加密【英文标题】:Upgrade mcrypt to OpenSSL encryption in SagePay form 【发布时间】:2020-04-12 10:52:37 【问题描述】:

我有一个收集数据的表单,然后将其提交给 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, "\0");

$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 需要在带有 PCKS#5 (AES-128-CBC-PKCS#5) 的 CBC 模式下使用 128 位块大小的 AES-128 加密进行加密。任何帮助都会非常有帮助。

【问题讨论】:

如果你想要填充,不要指定 OPENSSL_ZERO_PADDING。 openssl_encrypt 默认使用 PKCS#7,相当于 PKCS#5 用于 AES。并且不要弄乱密文。如果你有尾随零字节,它们应该在那里。 【参考方案1】:

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

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

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

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

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

function encryptAes_new ($string, $key) 
  $key = str_pad($key,16,"\0"); # 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 填充仅处理 64 位/8 字节块,PKCS7 将其扩展为其他大小,但 2017 年的 PKCS5v2.1 也将其扩展为引用 CMS,即 PKCS7 的 IETF 版本,因此它们是现在一样。)

【讨论】:

感谢 Dave 的解释和代码 sn-p,我得以实现并工作。

以上是关于以 SagePay 形式将 mcrypt 升级为 OpenSSL 加密的主要内容,如果未能解决你的问题,请参考以下文章

升级到 El Capitan OS 后 mcrypt 出错

Laravel 升级到 4.2 - 需要 mcrypt 扩展

Omnipay 和 Sagepay 服务器

如何在 MacOS 12 上为 php 7.1 安装 mcrypt 扩展以进行 Laravel 开发?

php升级到7.+之后openssl替代mcrypt实现解密

升级到 Mac OSX Sierra 时面临 mcrypt 错误问题