PHP 7.2 openssl_encrypt 和 mcrypt_encrypt 生成不同的值

PHP 7.2 openssl_encrypt and mcrypt_encrypt generate different values

我正在将项目中加密数据和使用 mcrypt 的所有功能迁移到 openssl。

进行测试我发现,使用相同的密钥加密相同的数据会得到不同的结果。

解密时,我用这两个函数都得到了正确的结果;问题是我与外部提供商共享此信息,并且只有在使用 mcrypt 加密后才能成功解密数据。

这是测试代码:

// Configuration.
$data = 'FOO';
$secret = '111222333444555666777888';
$iv = 'ABCDEFGH';

// Encrytp & decrypt with mcrypt.
$encMcrypt = bin2hex(mcrypt_encrypt(MCRYPT_3DES, $secret, utf8_encode($data),  MCRYPT_MODE_CBC, $iv));
$decMcrypt = mcrypt_decrypt(MCRYPT_3DES, $secret, hex2bin($encMcrypt), MCRYPT_MODE_CBC, $iv);

// Encrytp & decrypt with openssl.
$encOpenSSL = bin2hex(openssl_encrypt(utf8_encode($data), 'DES-EDE3-CBC', $secret, OPENSSL_RAW_DATA, $iv));
$decOpenSSL = openssl_decrypt(hex2bin($encOpenSSL), 'DES-EDE3-CBC', $secret, OPENSSL_RAW_DATA, $iv);

// Result.
echo "mcrypt encrypt: $encMcrypt <br>";
echo "openssl encrypt: $encOpenSSL <br>";
echo "mcrypt decrypt: $decMcrypt <br>";
echo "openssl decrypt: $decOpenSSL";

结果:

mcrypt encrypt: 3f9bd8d5f844ff67
openssl encrypt: b2f4b9aeb07e1ca4
mcrypt decrypt: FOO
openssl decrypt: FOO

有谁知道这是为什么,因为他们得到不同的结果?

谢谢!

据我所知,这两种方法使用不同的算法,请参阅 。您应该尝试使用 des-ede3 作为 openssl 密码方法,这相当于 mcrypt 的 MCRYPT_3DES.

Mcrypt 和 OpenSSL 是两种不同的密码,因此它们以不同的方式加密和解密数据。

您可以使用 openSSL 的 des-ede3 方法,但实际上您应该使用 AES:Security Comparison of AES and DES

如果您的外部提供商使用 mcrypt_decrypt 解密您发送给他们的数据,那么他们将无法解密使用 openssl 加密的数据,反之亦然。

如果您转向更安全的 AES openSSL 加密,您的外部提供商也需要更改解密数据的方式。

(附带说明一下,您转向 openssl 是件好事 - mcrypt 是大约 php 7.1 的废弃软件)

不同的是mcrypt_encrypt / mcrypt_decrypt uses Zero-Padding and openssl_encrypt / openssl_decrypt uses PKCS7-Padding。这可以通过为 openssl 应用零填充来轻松验证:为此,必须使用标志 OPENSSL_ZERO_PADDING 禁用 PKCS7-Padding(重要:尽管名称,这个标志并不意味着使用了零填充,而是根本没有应用填充),并且明文必须用 0 值填充到块大小的下一个整数倍(8 bytes for Triple-DES)除非长度已经对应于块大小的整数倍:

<?php

function zeroPadding($data, $size) {
    $oversize = strlen($data) % $size;
    return $oversize == 0 ? $data : ($data . str_repeat("[=10=]", $size - $oversize)); 
}

// Something is wronguration.
$data = 'FOO';

$secret = '111222333444555666777888';
$iv = 'ABCDEFGH';

// Encrytp & decrypt with mcrypt.
$encMcrypt = bin2hex(mcrypt_encrypt(MCRYPT_3DES, $secret, utf8_encode($data), MCRYPT_MODE_CBC, $iv));
$decMcrypt = mcrypt_decrypt(MCRYPT_3DES, $secret, hex2bin($encMcrypt), MCRYPT_MODE_CBC, $iv);

// Encrytp & decrypt with openssl.
$encOpenSSLZeroPadding = bin2hex(openssl_encrypt(utf8_encode(zeroPadding($data, 8)), 'DES-EDE3-CBC', $secret, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv));
$decOpenSSLZeroPadding = openssl_decrypt(hex2bin($encOpenSSLZeroPadding), 'DES-EDE3-CBC', $secret, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);

// Result.
echo "data padded: " . bin2hex(zeroPadding($data, 8)) . "<br>";
echo "mcrypt encrypt: $encMcrypt <br>";
echo "openssl encrypt: $encOpenSSLZeroPadding <br>";
echo "mcrypt decrypt: $decMcrypt <br>";
echo "mcrypt decrypt: " . bin2hex($decMcrypt) . "<br>";
echo "openssl decrypt: $decOpenSSLZeroPadding" . "<br>";
echo "openssl decrypt: " . bin2hex($decOpenSSLZeroPadding);

具有以下输出:

data padded: 464f4f0000000000
mcrypt encrypt: 3f9bd8d5f844ff67
openssl encrypt: 3f9bd8d5f844ff67
mcrypt decrypt: FOO
mcrypt decrypt: 464f4f0000000000
openssl decrypt: FOO
openssl decrypt: 464f4f0000000000

可以在 mcrypt 上下文中使用 PKCS7 填充,而不是在 openssl 上下文中使用零填充。无论使用这两种变体中的哪一种,具有相同的填充 mcryptopenssl 结果是相同的!

应该注意(参见十六进制输出)mcrypt 在解密期间不会删除零填充(不像 openssl 在解密期间删除 PKCS7-Padding)。此外,与 PKCS7-Padding 相比,零填充 unreliable。如果要求允许(在您的情况下可能不是这种情况,因为外部提供者),因此应该使用 PKCS7-Padding。

此外,两个已发布的变体都使用相同的算法,即 Triple-DES in CBC-mode. Triple-DES has a blocksize of 8 bytes and a keysize of 24 bytes. Triple-DES is not identical to DES,但基于 DES,因为它由三个 DES 运行组成(加密-解密-加密 = ede)。 mcrypt 指定 Triple-DES/CBC 有两个参数,MCRYPT_3DES (Triple-DES) 和 MCRYPT_MODE_CBC (CBC-mode),而 openssl 只使用一个参数,DES-EDE3-CBC.

Triple-DES 有多个 Keying-Options3TDEA 使用三个独立的 DES 密钥,并在 openssl 的上下文中指定 DES-EDE3-CBC,它需要一个 24 字节的密钥,2TDEA 使用两个独立的密钥和可以在 openssl 的上下文中指定,或者用 DES-EDE-CBC 指定,它需要一个 16 字节的键。

Triple-DES 比现代 AES 慢得多,但具有相当的安全性。与填充一样,如果可能,您应该切换到 AES。