将Mcrypt升级为SagePay网关形式的OpenSSL加密

问题描述

当前,我们以SagePay网关形式具有mcyrpt加密,PHP 7.4中不提供或不支持该加密。

对于我们如何将下面的代码从mcyrpt更改为OpenSSL,会有任何想法吗?

    // 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)
{
    // 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))
    {
        throw new SagepayApiException('Invalid encryption string');
    }
    $strIn = pack('H*',$hex);

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

下面是我们正在尝试的OpenSSL代码,但需要进行一些调整,对于需要为使OpenSSL代码代替上面的mcyrpt而需要更改的任何建议,将不胜感激!

解决方法

发布的 openssl 代码有效,即可以用encrypt_openssl解密用decrypt_openssl加密的纯文本。但是, openssl 代码与 mcrypt 代码在功能上并不相同,即,用 mcrypt 代码加密的数据无法用解密。 openssl 代码,反之亦然。

我对工作流程不太清楚,但我怀疑SagePay网关还使用了 mcrypt 代码的加密和解密逻辑,因此进行通信时,{ {1}}代码在功能上与openssl代码相同(尽管mcrypt代码的实现比 mcrypt 代码更安全)。

不幸的是,代码的重要部分没有发布,例如加密方法不完整,openssladdPKCS5Padding完全丢失,因此必须使用部分假设。

关于 mcrypt 代码适用:

  • AES-128用于CBC模式。 AES-128需要一个16个字节的密钥。
  • 使用IV作为密钥。请注意,这通常是不安全的,here
  • mcrypt 本身在隐式应用零填充,如果纯文本的长度已经对应于块大小的整数倍,则不执行填充。由于首先完成了使用PKCS5(实际上是PKCS7)的自定义填充,因此 mcrypt 填充不会生效,因此有效地应用了PKCS5填充。不幸的是, addPKCS5Padding removePKCS5Padding 的实现未发布,因此无法检查它们是否都符合标准。解密后,将删除PKCS5填充(不同于 mcrypt 的内置零填充)。
  • 加密使用大写字母对密文进行十六进制编码,并以removePKCS5Padding为前缀。

关于 openssl ,必须考虑以下几点:

  • openssl 默认情况下使用PKCS7填充(在解密过程中默认情况下删除),其功能与 mcrypt 代码中使用的填充相同(至少在 mcrypt 代码中使用的自定义PKCS5填充与标准匹配)。
  • @返回默认编码的密文Base64(并且 openssl_decrypt 也希望采用这种编码)。标志openssl_encrypt导致 openssl_encrypt 将数据作为原始数据返回(并且 openssl_decrypt 也期望原始数据)

在所假设的范围内,以下 openssl 实现在功能上与 mcrypt 实现相同。必须使用16个字节的密钥:

OPENSSL_RAW_DATA

编辑:

两种方法function encrypt_openssl($msg,$key,$iv) { $encryptedMessage = openssl_encrypt($msg,'AES-128-CBC',OPENSSL_RAW_DATA,$iv); return '@' . strtoupper(bin2hex($encryptedMessage)); } function decrypt_openssl($encMsg,$iv) { $encMsg = substr($encMsg,1); // your validation stuff $encMsg = hex2bin($encMsg); return openssl_decrypt($encMsg,$iv); } addPKCS5Padding符合PKCS7标准。无需修改。这些功能不得在 openssl 函数中使用,因为 openssl 隐式使用PKCS7填充,因此将其填充两次,这将导致不同的密文。

SagePay 代码的加密与我发布的 openssl 函数的加密可以很容易地测试:

使用 SagePay 代码进行加密(可以执行该代码,例如here选择V 7.0.x或更低版本)

removePKCS5Padding

输出:

<?php
class SagepayApiException extends Exception {}

class SagePayTest {

    static public function encryptAes($string,$key)
    {
        $string = self::addPKCS5Padding($string);
        $crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$string,MCRYPT_MODE_CBC,$key);
        return "@" . strtoupper(bin2hex($crypt));
    }
    
    static public function decryptAes($strIn,$password)
    {
        $strInitVector = $password;
        $hex = substr($strIn,1);
        if (!preg_match('/^[0-9a-fA-F]+$/',$hex))
        {
            throw new SagepayApiException('Invalid encryption string');
        }
        $strIn = pack('H*',$hex);
        $string = mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$password,$strIn,$strInitVector);
        return self::removePKCS5Padding($string);
    }
    static protected function addPKCS5Padding($input)
    {
        $blockSize = 16;
        $padd = "";
        $length = $blockSize - (strlen($input) % $blockSize);
        for ($i = 1; $i <= $length; $i++)
        {
            $padd .= chr($length);
        }
        return $input . $padd;
    }
    static protected function removePKCS5Padding($input)
    {
        $blockSize = 16;
        $padChar = ord($input[strlen($input) - 1]);
        if ($padChar > $blockSize)
        {
            throw new SagepayApiException('Invalid encryption string');
        }
        if (strspn($input,chr($padChar),strlen($input) - $padChar) != $padChar)
        {
            throw new SagepayApiException('Invalid encryption string');
        }
        $unpadded = substr($input,(-1) * $padChar);
        if (preg_match('/[[:^print:]]/',$unpadded))
        {
            throw new SagepayApiException('Invalid encryption string');
        }
        return $unpadded;
    }
}

$plaintext = "The quick brown fox jumps over the lazy dog";
$testkey = "95tjbs763khd9zh7";
$encrypted = SagePayTest::encryptAes($plaintext,$testkey);
$decrypted = SagePayTest::decryptAes($encrypted,$testkey);
print("Ciphertext (hex): " . $encrypted . PHP_EOL);
print("Decrypted  (hex):  " . bin2hex($decrypted) . PHP_EOL);
print("Decrypted       :  " . $decrypted . PHP_EOL);
?>

使用 openssl 函数进行加密(可以执行代码here

Ciphertext (hex): @67D4D4E0019BA7D0E75CC92C41488EF13613C1ED8A95B60E56347D1514AC5D7AF780F4314CA047A487AB67DF2D94174A
Decrypted  (hex):  54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67
Decrypted       :  The quick brown fox jumps over the lazy dog

输出:

<?php
function encrypt_openssl($msg,$iv); 
}

$plaintext = "The quick brown fox jumps over the lazy dog";
$testkey = "95tjbs763khd9zh7";
$encrypted = encrypt_openssl($plaintext,$testkey,$testkey);
$decrypted = decrypt_openssl($encrypted,$testkey);
print("Ciphertext (hex): " . $encrypted . PHP_EOL);
print("Decrypted  (hex):  " . bin2hex($decrypted) . PHP_EOL);
print("Decrypted       :  " . $decrypted . PHP_EOL);
?>

加密和解密都匹配。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...