在Python和PHP上解密AES256

时间:2019-03-08 12:04:38

标签: 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密钥必须为16、24或32个字节长

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

1 个答案:

答案 0 :(得分:4)

您正在定义一个64个字符的密钥;因为这64个字符都是十六进制数字,所以这里的openssl_encrypt()不会以任何方式对十六进制字符进行解码,它会逐字使用这些字符。

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

如果在Python中将密钥缩减为32个字符,则可以成功解密消息;在两种情况下,如果将密钥从十六进制转换为字节,则可以成功解密消息:

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

我强烈建议解码时不要截断; 32个十六进制字符的熵要少得多(字节覆盖的数量与32个十六进制字符 squared 中可编码的值的数量一样多,2等于256的幂,而2等于128的幂)。 / p>

由于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块大小(16字节),您需要再次在Python端PyCrypto {{1 }}方法无法为您完成此操作:

AES.decrypt()

但是请注意,PyCrypto项目在 6年的新版本中尚未出现,因此不应再被视为安全的。您确实要在此处使用cryptography project

# 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()

演示,首先是PHP:

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()

然后使用Python; $ 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 函数的定义如上:

cryptography