为了账号安全,请及时绑定邮箱和手机立即绑定

PHP7.4:OpenSSL AES-CFB 加密与 Python 不同

PHP7.4:OpenSSL AES-CFB 加密与 Python 不同

PHP
呼如林 2023-08-11 17:13:21
我正在尝试使用 PHP7.4 复制一段使用 Pycryptodome 进行 AES-128-CFB 加密的 python 代码。为此,我使用 PHP 的 openssl_encrypt 内置函数。我尝试了多种配置参数和 CFB 模式,但始终得到不同的结果。我发现pycryptodomes CFB实现似乎使用8位段大小,这应该是aes-128-cfb8PHP的openssl实现中的模式。IV 故意固定为 0,所以请忽略它不安全的事实。这是我想要复制的代码,后面是尝试用不同方法复制结果的 PHP 代码。有些东西告诉我这与 PHP 的“字节处理”有关,因为 python 区分字节字符串(由 返回.encode('utf-8'))和字符串。最后你可以看到两个代码的输出:Python代码:import hashlibfrom Crypto.Cipher import AESkey = 'testKey'IV = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'ENC_KEY = hashlib.md5(key.encode('utf-8')).hexdigest()print('key: "' + key + '"')print('hashedKey: ' + ENC_KEY)obj = AES.new(ENC_KEY.encode("utf8"), AES.MODE_CFB, IV.encode("utf8"))test_data = 'test'print('encrypting "' + test_data + '"')encData = obj.encrypt(test_data.encode("utf8"))print('encData: ' + encData.hex())PHP代码:function encTest($testStr, $ENC_KEY){    $iv = hex2bin('00000000000000000000000000000000');    echo "aes-128-cfb8-1: ".bin2hex(openssl_encrypt($testStr, 'aes-128-cfb8', $ENC_KEY, OPENSSL_RAW_DATA, $iv))."\n";    echo "aes-128-cfb1-1: ".bin2hex(openssl_encrypt($testStr, 'aes-128-cfb1', $ENC_KEY, OPENSSL_RAW_DATA, $iv))."\n";    echo "aes-128-cfb-1: ".bin2hex(openssl_encrypt($testStr, 'aes-128-cfb', $ENC_KEY, OPENSSL_RAW_DATA, $iv))."\n";    echo "\n";    echo "aes-128-cfb8-2: ".bin2hex(openssl_encrypt($testStr, 'aes-128-cfb8', $ENC_KEY, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv))."\n";    echo "aes-128-cfb1-2: ".bin2hex(openssl_encrypt($testStr, 'aes-128-cfb1', $ENC_KEY, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv))."\n";    echo "aes-128-cfb-2: ".bin2hex(openssl_encrypt($testStr, 'aes-128-cfb', $ENC_KEY, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv))."\n";    echo "\n";}
查看完整描述

1 回答

?
墨色风雨

TA贡献1853条经验 获得超6个赞

在 PHP 代码中(更准确地说是openssl_encrypt),明确指定了 AES 变体,例如当前情况下的aes-128-...,即 PHP 使用 AES-128。太长的键会被截断,太短的键会用0值填充。由于hashPHP 代码中的方法以十六进制字符串形式返回其结果,因此 16 字节 MD5 哈希值由 32 个字符(32 字节)表示,即在当前情况下 PHP 使用密钥的前 16 个字节 (AES-128)。

Python 代码中的方法hexdigest也以十六进制字符串形式返回结果。然而,在Python代码中(更准确地说是PyCryptodome),AES变体由密钥大小指定,即Python代码使用完整的32字节密钥,因此使用AES-256。

不同的密钥和 AES 变体是导致不同结果的主要原因。要解决此问题,两个代码中必须使用相同的密钥和 AES 变体:

  • 选项 1 是在 Python 代码中也使用 AES-128。这可以通过以下更改来实现:

    obj = AES.new(ENC_KEY[:16].encode("utf8"), AES.MODE_CFB, IV.encode("utf8"))

    然后输出b0016a55是按照 PHP 代码的结果aes-128-cfb8

  • 选项 2 是在 PHP 代码中也使用 AES-256。aes-128...这可以通过替换来完成aes-256...然后输出是

    aes-256-cfb8-1: 117c1974
    aes-256-cfb1-1: 54096db1
    aes-256-cfb-1 : 11bfdaa9

正如预期的那样, 的输出117c1974aes-128-cfb8Python 代码的原始值相匹配。


CFB 模式将分组密码更改为流密码。从而n在每个加密步骤中对位进行加密,这称为CFBn

PHP 中也使用术语CFBn(或),即表示 1 位、8 位(= 1 字节)和整个块(16 字节)的加密。在 Python 中,每步的位数用 指定。cfbnCFB1CFB8CFBsegment_size

...-cfb8即PHP 中的对应项是 Python 中, PHP 中segment_size = 8的对应项是Python 中。...-cfbsegment_size = 128

下面假设两个代码中使用相同的密钥和相同的 AES 变体。

由于是默认值,Python 代码的结果与 PHP 代码的segment_size = 8结果相同。...-cfb8如果选择在 Python 代码中,则结果与在 PHP 代码中segement_size = 128相同。...-cfb但是,在 PyCryptodome 中,该值segment_size必须是 8 的整数倍,否则错误消息“segment_size”必须为正数并显示8 位的倍数。因此,CFB1PyCryptodome 不支持该模式。


另请注意:

  • 摘要的结果也可以在两种代码中以二进制形式返回,而不是以十六进制字符串形式返回。为此,hash必须将 PHP 方法的第三个参数设置为TRUE(默认值FALSE:)。在Python中,只需使用digest方法而不是hexdigest.

  • 在 PHP 代码中,对于像 CFB 这样的流密码模式,填充会自动禁用,因此标志OPENSSL_ZERO_PADDING(可用于显式禁用填充)没有任何区别。

  • utf8_encode允许您从 ISO-8859-1 编码转换为 UTF-8,但由于它$ENC_KEY由字母数字字符(十六进制编码)组成,因此没有任何效果。但是,一般来说,任意二进制数据(例如摘要的结果)不得采用 UTF8 编码,因为这会损坏数据。还有其他用于此目的的编码,例如 Base64。如果摘要的结果以二进制形式返回(参见第一点),则不能执行 UTF8 编码。

  • 在 CFB 模式的上下文中,旧版 PyCrypto 库中存在一个错误,该错误要求明文的长度是段大小的整数倍。否则会出现以下错误:输入字符串的长度必须是段大小 16 的倍数。


查看完整回答
反对 回复 2023-08-11
  • 1 回答
  • 0 关注
  • 184 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信