使用 mysql AES 从 Django 加密和解密

Using mysql AES encrypt and decrypt from Django

在我的 Django 应用程序中,我想使用 mysql 数据库中的 AES_ENCRYPTAES_DECRYPT 来加密和解密内容。
我知道 python 的加密包支持 AES,但加密 AES 不会产生与 mysql AES 相同的结果,尽管我确保两者都使用 ECB 模式。
所以,现在我正在这样做:

sql = "select 1 as id, AES_ENCRYPT(my_field, '16-bytes encryption key') as field_enc from appname_table"
encrypted_fields = MyModel.objects.raw(sql)

这让我得到了加密后的字段值,并且工作正常。问题是AES算法加密最终结果有很多不可打印的字符,看起来像这样:

encrypted_fields[3].field_enc
'\x88\xc5\xe4\xa0c?\xf8\x16|^1JB\x83{\xdf'
print(encrypted_fields[3].field_enc)
���c?�|^1JB�{�

所以,现在当我尝试使用相同的值来解密它时,mysql 回复了一个错误

"OperationalError: (1300, "Invalid utf8 character string: '\x88\xC5\xE4\xA0c'")"

我想这是因为我试图将不可打印的字符发送到 mysql 查询。

那么,我该如何处理呢?
请注意,我必须使用 MySQL 函数,因为我使用这些函数加密了一些数据库字段,所以我需要使用相同的函数对它们进行解密,因为 Crypto 的 AES 没有得到相同的结果。 Crypto 一直要求要加密的文本必须像密钥一样长 16 个字节,而 mysql 不需要。

我还尝试查看 mysql 是否在加密前对文本使用某种填充,但它没有说明他们在 webpage 上填充的字符:

The str and crypt_str arguments can be any length, and padding is automatically added to str so it is a multiple of a block as required by block-based algorithms such as AES. This padding is automatically removed by the AES_DECRYPT() function

经过多次谷歌搜索,我发现了这个 blog article 关于在 PHP 中复制 mysql AES 加密。
他们的关键原来是用于填充的字符是具有等于 AES 块大小和文本长度之间的长度差的 ascii 码的字符。

c = chr(16 - len(text_to_be_encrypted))
def align_str(s, n, char):
    if len(s) < n:
        diff = n - len(s)
        for i in range(diff):
            s += char
    return s
s = align_str(text_to_be_encrypted, 16, c)
e = encryption_suite.encrypt(s)

它奏效了。它产生与 mysql 加密相同的输出。

在这个关于 ECB 的 article about replicating mysql AES Encryption in PHP, i found out that mysql uses ECB mode for AES encryption. Pycrypto library provides ECB mode for AES encryption.According to pycrypto documentation 中,输入必须与右边界对齐,并且其大小必须是 block_size 的倍数(即 AES 为 16 字节)。 pycryptodome 库 'pad' 中的一个函数在这里起到了作用,但它需要字节串作为输入。

下面提供的代码在 python 语言中给出的 AES 加密结果与在 mysql 对应语言中得到的结果相同。

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
def aes_encrypt(data):
    key = b'1234567812345678' # 16 byte key
    obj = AES.new(key, AES.MODE_ECB)
    padded_data = pad(bytes(data,'utf-8'),16)   
    encrypted_data = obj.encrypt(padded_data)
    return encrypted_data