php - 使用 openssl_encrypt AES-CBC 实现 Python 到 PHP 兼容的 AES 加密

标签 php python-3.x encryption interop aes

我有一个 Python 应用程序和 PHP 网站,它们通过某些特定的网络层发送消息进行通信。我的任务是使用该 channel 发送 AES 加密和 Base64 编码的所有消息。加密 key 已手动预先共享给双方。

在我的 PHP 中,我使用此代码创建名为 $payload 的最终消息文本:

 $key = substr('abdsbfuibewuiuizasbfeuiwhfashgfhj56urfgh56rt7856rh', 0, 32);
 $magic = 'THISISANENCRYPTEDMESSAGE';

 function crypted($data) {
      global $key, $magic;

       // serialize
       $payload = json_encode($data);

       // encrypt and get base64 string with padding (==):
       $payload = @openssl_encrypt($payload, 'AES-192-CBC', $key);

       // prepend with magic
       $payload = $magic.$payload;
       return $payload;
    }

我在 Python 应用程序中收到这样的消息,剥离了魔力,获取了 Base64 字节数据。问题是我找不到示例来制作兼容的 AES 密码来解码此消息。

Key 和“Magic”只是双方预先共享且已知的值,这是否正确?我需要静脉注入(inject)吗?

这是来自 SO 的 Python 解决方案,它不适用于我的加密消息。

from base64 import b64encode, b64decode
from Crypto.Cipher import AES


class AESCipher:

    class InvalidBlockSizeError(Exception):
        """Raised for invalid block sizes"""
        pass

    def __init__(self, key):
        self.key = key
        self.iv = bytes(key[0:16], 'utf-8')

    def __pad(self, text):
        text_length = len(text)
        amount_to_pad = AES.block_size - (text_length % AES.block_size)
        if amount_to_pad == 0:
            amount_to_pad = AES.block_size
        pad = chr(amount_to_pad)
        return text + pad * amount_to_pad

    def __unpad(self, text):
        pad = ord(text[-1])
        return text[:-pad]

    def encrypt( self, raw ):
        raw = self.__pad(raw)
        cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
        return b64encode(cipher.encrypt(raw))

    def decrypt( self, enc ):
        enc = b64decode(enc)
        cipher = AES.new(self.key, AES.MODE_CBC, self.iv )
        r = cipher.decrypt(enc)  # type: bytes
        return self.__unpad(r.decode("utf-8", errors='strict'))

由于解码问题,最后一行失败。 “忽略”解码模式返回空字符串。

# with magic: "THISISANENCRYPTEDMESSAGE8wZVLZpm7UNyUf26Kds9Gwl2TBsPRo3zYDFQ59405wI="
# contains: {'test': 'hello world'}
payload = '8wZVLZpm7UNyUf26Kds9Gwl2TBsPRo3zYDFQ59405wI='

aes = AESCipher('abdsbfuibewuiuizasbfeuiwhfashgfh')
print(aes.decrypt(payload))

加薪:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "../test.py", line 36, in decrypt
    return self.__unpad(cipher.decrypt(enc).decode("utf-8"))
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x9e in position 0: invalid start byte

我错过了什么?

最佳答案

您正在使用Cipher Block Chaining ,但没有将 IV 传递给 openssl_encrypt();这意味着 IV 是 NUL 字节的 16 倍。但是您的 Python 代码使用 key 作为 IV,因此会产生完全不同的解密结果。

接下来,您选择了 AES-192-CBC,而不是 AES-256-CBC,因此 key 仅使用 192 位。 192 位 == 24 字节,而不是您想象的 32 位。

您还需要完全删除__unpad()调用,加密数据中没有填充,在解密之前从末尾删除数据只会导致解密失败。

因此,要在 Python 端解密,请使用 24 个字符作为 key ,给出 16 倍 \x00 的 IV,并传入您解码的所有数据Base64:

>>> from Crypto.Cipher import AES
>>> from base64 import b64decode
>>> key = 'abdsbfuibewuiuizasbfeuiwhfashgfh'[:24]
>>> key
'abdsbfuibewuiuizasbfeuiw'
>>> payload = '8wZVLZpm7UNyUf26Kds9Gwl2TBsPRo3zYDFQ59405wI='
>>> enc = b64decode(payload)
>>> cipher = AES.new(key, AES.MODE_CBC, '\x00' * 16)
>>> cipher.decrypt(enc)
b'{"test":"hello world"}\n\n\n\n\n\n\n\n\n\n'

如果您想使用 key 的完整 32 个字符,请改用 AES-256-CBC。

您确实想要生成一个随机 IV,以便窥探流量的人无法确定模式(相同的有效负载每次都会生成相同的加密消息)。生成 IV,将其包含在您发送的数据中,然后在 Python 端提取它以传递给 AES.new() 函数。

关于php - 使用 openssl_encrypt AES-CBC 实现 Python 到 PHP 兼容的 AES 加密,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40820661/

相关文章:

python-3.x - 如何在Python Flask中检查request.form.get值是否为null?

mysql - 反向加入Django

php - 在 PHP 中获取私钥 .pfx

c++ - AES-gcm 加密的 IV 中是否存在不起作用的值?

mysql - 是否可以逆向 mysql aes_crypt 来找到 key ?

php - 如何自动创建 key (如 FBAppid)进行注册?

php - 我想要正则表达式删除 PostgreSQL 版本 9.3.5 中的括号

python - 在 python 中写入 csv 文件时有没有办法使用索引

php - mysql_real_escape_string() 会取消 stripslashes() 吗?

php - htaccess 显示缩短的 url