php - 在 Python 和 PHP 上解密 AES256

标签 php python cryptography aes

我在 AES 加密、使用 PHP 加密和 Python 解密方面遇到问题。

为了加密,我使用这个 PHP 函数:

function cryptpass($arg1) {

    $k = '61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6';
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $cipher = base64_encode(openssl_encrypt($arg1,'aes-256-cbc',$k,OPENSSL_RAW_DATA,$iv));
    return urlencode(base64_encode('{"cipher":"'.$cipher.'","i":"'.base64_encode($iv).'"}'));
}

我使用这个Python代码来解密:

def decryptpass(info):
   key = '61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6'
   data = json.loads(base64.b64decode(info))
   iv = base64.b64decode(data.get('i'))
   cipher = AES.new(key,AES.MODE_CBC,iv)
   return cipher.decrypt(data.get('cipher'))

但是运行这段代码时,出现以下错误:

ValueError: AES key must be either 16, 24, or 32 bytes long

我知道我的 key 有 64 个字节,但是 PHP 加密是如何使用它的?我尝试从 key 中删除最后 32 个字符,但这不起作用。

最佳答案

您正在定义 64 个字符的 key ;这 64 个字符是十六进制数字并不存在,openssl_encrypt() 不会以任何方式解码十六进制字符,它会逐字使用这些字符。

但是,AES-256 仅采用 32 字节(== 256 位)而不是 64 字节的 key ,并且 openssl_encrypt() 静默截断 key 。另一方面,PyCrypto AES.new() 方法明确告诉您 key 太长,提醒您此处的错误,即您应该首先将十六进制 key 解码为字节.

如果您将 Python 中的 key 减少到 32 个字符,或者如果您在这两种情况下将 key 从十六进制转换为字节,则可以成功解密消息:

$k = hex2bin('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6');
key = bytes.fromhex('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6')

我强烈建议解码而不是截断; 32 个十六进制字符的熵要少得多(字节覆盖的可能值与 32 个十六进制字符平方中可编码的值的数量一样多,2 的 256 次方与 2 的 128 次方)。

因为 openssl_encrypt() 还对返回值进行 Base64 编码,因此您需要在 Python 端对 cipher 值进行 Base64 解码:

>>> data = json.loads(base64.b64decode(info))
>>> data
{'cipher': 'Iu9VgH8DdxHdQgnq8o23ew==', 'i': 'Vz+wy5VS6toNHx7MEYl+/A=='}
# base64:   ^^^^^^^^^^^^^^^^^^^^^^^^         ^^^^^^^^^^^^^^^^^^^^^^^^

最后,openssl_encrypt() 添加 PKCS#7 padding到加密消息以使其适合 AES block 大小(16 字节),您需要在 Python 端再次删除该填充,PyCrypto AES.decrypt() 方法不会执行此操作你:

# Decode from hex to create a key 256 bits (32 bytes) long:
key = bytes.fromhex('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6')
# or, if you don't use hex2bin in PHP, truncate to 32 characters
# key = b'61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6'[:32]


def decryptpass(info):
    data = json.loads(base64.b64decode(info))
    iv = base64.b64decode(data['i'])
    cipher = AES.new(key, AES.MODE_CBC, iv)
    padded = cipher.decrypt(base64.b64decode(data['cipher']))
    # manual PKCS#7 unpadding
    return padded[:-padded[-1:]].decode()

但请注意,PyCrypto 项目已经有 6 年没有发布新版本了,因此不应再相信它是安全的。您确实想使用 cryptography project此处改为:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend

key = bytes.fromhex('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6')

def decrypt_aes_256(key, iv, encrypted):
    decryptor = Cipher(
        algorithms.AES(key), modes.CBC(iv), default_backend()
    ).decryptor()
    unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
    decrypted = decryptor.update(encrypted) + decryptor.finalize()
    return unpadder.update(decrypted) + unpadder.finalize()

def decryptpass(info):
    data = json.loads(base64.b64decode(info))
    iv = base64.b64decode(data['i'])
    encrypted = base64.b64decode(data['cipher'])
    return decrypt_aes_256(key, iv, encrypted).decode()

演示,首先使用 PHP:

$ php -a
Interactive shell

php > function cryptpass($arg1) {
php {     $k = hex2bin('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6');
php {     $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
php {     $cipher = base64_encode(openssl_encrypt($arg1,'aes-256-cbc',$k,OPENSSL_RAW_DATA,$iv));
php {     return urlencode(base64_encode('{"cipher":"'.$cipher.'","i":"'.base64_encode($iv).'"}'));
php { }
php > echo cryptpass("Hello, world!");
eyJjaXBoZXIiOiJJdTlWZ0g4RGR4SGRRZ25xOG8yM2V3PT0iLCJpIjoiVnord3k1VlM2dG9OSHg3TUVZbCsvQT09In0%3D

然后在Python中;具有如上定义的加密函数:

>>> from urllib.parse import unquote
>>> info = unquote("eyJjaXBoZXIiOiJJdTlWZ0g4RGR4SGRRZ25xOG8yM2V3PT0iLCJpIjoiVnord3k1VlM2dG9OSHg3TUVZbCsvQT09In0%3D")
>>> decryptpass(info)
'Hello, world!'

关于php - 在 Python 和 PHP 上解密 AES256,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55062897/

相关文章:

python - 通过 SFTP 检索文件时为 "IOError: size mismatch in get!"

python - python twisted framework HttpClient是否访问代理?

java - 如何列出可用的密码算法?

javascript - Forge:加密大文件

php - 使用多态性过滤 MySQL 结果

javascript - 单击按钮时仅显示 $(this) 行表格数据

PHP 返回(值);对比返回值;

PHP:迭代多个数组并构建 SQL INSERT 查询

python - 在组中查找不同的计数

encryption - 将 Python AES 加密路由移植到 Golang